Einige GC-Tunings am Ahnensystem vorgenommen und die Pausenzeit um 90 % reduziert | JD Cloud-Technikteam

Problembeschreibung

Ein Regel-Engine-System des Unternehmens wird jedes Mal, wenn eine Version gestartet wird, manuell vorgewärmt. Nach Abschluss des Vorheizens wird beim Einschalten des Datenverkehrs gelegentlich 1-2 Sekunden lang Young GC angezeigt (der Datenverkehr ist nicht groß und alle Zeit unter LB Diese Situation tritt auf jedem Knoten auf)

Nach dieser langen Pause kehrte die Pausenzeit jedes GC der jungen Generation auf 20–100 ms zurück.

Obwohl 2 Sekunden keine lange Zeit zu sein scheinen, dauert es für jede Ausführung der Regel-Engine nur wenige Millisekunden. Wer kann das tolerieren? Und sobald dieses Ding eine Zeitüberschreitung aufweist, kann es sein, dass auch die Bestellung eine Zeitüberschreitung erfährt und fehlschlägt!

Problemanalyse

Nach der Analyse des GC-Protokolls des Systems wurde festgestellt, dass die 2-sekündige Pause in der Young-GC-Phase auftrat und jedes Mal, wenn eine lange Pause in der Young-GC-Phase auftritt, mit der Heraufstufung von Objekten der neuen Generation einhergeht.

Kern-JVM-Parameter (Oracle JDK7)

-Xms10G 
-Xmx10G 
-XX:NewSize=4G 
-XX:PermSize=1g 
-XX:MaxPermSize=4g 
-XX:+



Manche Leute fragen sich vielleicht, warum so viel Speicher? Der Ahnencode kann nicht ausgeführt werden, da der Speicher zu klein ist!

Erstes GC-Protokoll der jungen Generation nach dem Start

2023-04-23T16:28:31.108+0800: [GC2023-04-23T16:28:31.108+0800: [ParNew2023-04-23T16:28:31.229+0800: [SoftReference, 0 refs, 0.0000950 secs]2023-04-23T16:28:31.229+0800: [WeakReference, 1156 refs, 0.0001040 secs]2023-04-23T16:28:31.229+0800: [FinalReference, 10410 refs, 0.0103720 secs]2023-04-23T16:28:31.240+0800: [PhantomReference, 286 refs, 2 refs, 0.0129420 secs]2023-04-23T16:28:31.253+0800: [JNI Weak Reference, 0.0000000 secs]
Desired survivor size 214728704 bytes, new threshold 1 (max 15)
- age   1:  315529928 bytes,  315529928 total
- age   2:   40956656 bytes,  356486584 total
- age   3:    8408040 bytes,  364894624 total
: 3544342K->374555K(3774912K), 0.1444710 secs] 3544342K->374555K(10066368K), 0.1446290 secs] [Times: user=1.46 sys=0.09, real=0.15 secs] 


Lange Pause, GC-Protokoll der jungen Generation

2023-04-23T17:18:28.514+0800: [GC2023-04-23T17:18:28.514+0800: [ParNew2023-04-23T17:18:29.975+0800: [SoftReference, 0 refs, 0.0000660 secs]2023-04-23T17:18:29.975+0800: [WeakReference, 1224 refs, 0.0001400 secs]2023-04-23T17:18:29.975+0800: [FinalReference, 8898 refs, 0.0149670 secs]2023-04-23T17:18:29.990+0800: [PhantomReference, 600 refs, 1 refs, 0.0344300 secs]2023-04-23T17:18:30.025+0800: [JNI Weak Reference, 0.0000210 secs]
Desired survivor size 214728704 bytes, new threshold 15 (max 15)
- age   1:   79203576 bytes,   79203576 total
: 3730075K->304371K(3774912K), 1.5114000 secs] 3730075K->676858K(10066368K), 1.5114870 secs] [Times: user=6.32 sys=0.58, real=1.51 secs] 


Dem GC-Protokoll dieser langen Pause nach zu urteilen, hat eine Heraufstufung stattgefunden. Nach dem Young GC wurden mehr als 363 Millionen Objekte auf die alte Generation heraufgestuft. Dieser Heraufstufungsvorgang sollte zeitaufwändig sein (ps: Ich habe den Safepoint-Grund überprüft und es gibt keine Ausnahme. )

Da es in den Protokollparametern keine Konfigurationsparameter gibt -XX:+PrintHeapAtGC, ist hier die manuell berechnete Aktionsgröße:

年轻代年轻变化 - 全堆容量变化 = 晋升大小
(304371K - 3730075K) - (676858K - 3730075K) = 372487K(363M)


Nächstes GC-Protokoll der jungen Generation

2023-04-23T17:23:39.749+0800: [GC2023-04-23T17:23:39.749+0800: [ParNew2023-04-23T17:23:39.774+0800: [SoftReference, 0 refs, 0.0000500 secs]2023-04-23T17:23:39.774+0800: [WeakReference, 3165 refs, 0.0002720 secs]2023-04-23T17:23:39.774+0800: [FinalReference, 3520 refs, 0.0021520 secs]2023-04-23T17:23:39.776+0800: [PhantomReference, 150 refs, 1 refs, 0.0051910 secs]2023-04-23T17:23:39.782+0800: [JNI Weak Reference, 0.0000100 secs]
Desired survivor size 214728704 bytes, new threshold 15 (max 15)
- age   1:   17076040 bytes,   17076040 total
- age   2:   40832336 bytes,   57908376 total
: 3659891K->90428K(3774912K), 0.0321300 secs] 4032378K->462914K(10066368K), 0.0322210 secs] [Times: user=0.30 sys=0.00, real=0.03 secs] 


Auf den ersten Blick scheint nichts falsch zu sein, aber nachdem ich sorgfältig darüber nachgedacht habe, stelle ich immer noch fest, dass etwas nicht stimmt. Warum erfolgte die Beförderung direkt nach dem Start des zweiten GC?

Bild.png

Es wird spekuliert, dass dies durch die dynamische Altersbestimmung verursacht werden sollte. Der Schwellenwert für das Aufstiegsalter in GC ist nicht auf 15 festgelegt, sondern wird von der JVM nach jedem GC dynamisch berechnet.

Mechanismus zur Förderung der jungen Generation

Um sich besser an die Speicherbedingungen verschiedener Programme anzupassen, erfordert die virtuelle Maschine nicht immer, dass das Alter des Objekts MaxTenuringThreshold erreichen muss, bevor es auf die alte Generation hochgestuft werden kann. Wenn die Summe der Größen aller Objekte der Das gleiche Alter im Survivor-Bereich ist größer als die Hälfte des Survivor-Bereichs. Das Alter von Objekten, die größer oder gleich diesem Alter sind, kann direkt in die alte Generation eintreten, ohne auf das in MaxTenuringThreshold erforderliche Alter warten zu müssen.

Das Buch „In-Depth Understanding of the Java Virtual Machine“ erwähnt, dass der Schwellenwert für das Alter der Objektförderung dynamisch bestimmt wird.

Nachdem ich jedoch andere Informationen konsultiert und überprüft hatte, stellte ich fest, dass es einige Diskrepanzen zwischen dieser und der Erklärung in „Vertiefendes Verständnis der Java Virtual Machine“ gibt.

Tatsächlich werden die Objekte nach Alter gruppiert und die Altersgruppe mit der größten Summe (kumulierter Wert, kleiner oder gleich der Gesamtgröße des Objekts mit dem aktuellen Alter) ausgewählt. Wenn die Summe der Gruppe größer als ist Bei der Hälfte der Hinterbliebenen wird die Altersgrenze für die Beförderung auf das Alter der Gruppe aktualisiert.

Hinweis: Es ist nicht so, dass eine Beförderung erfolgt, wenn mehr als die Hälfte der Überlebenden überschritten wird. Mehr als die Hälfte der Überlebenden setzt nur den Beförderungsschwellenwert (Schwellenwert) zurück und der neue Schwellenwert wird im nächsten GC verwendet.

3544342K->374555K(3774912K), 0.1444710 secs] 年轻代

3544342K->374555K(10066368K), 0.1446290 secs] 全堆


Diese Schlussfolgerung kann auch aus dem ersten GC-Protokoll oben bewiesen werden. In diesem GC sind die Speicheränderungen des gesamten Heaps und die Speicheränderungen der jungen Generation gleich, sodass keine Objektförderung erfolgt.

Genau wie im Protokoll oben setzt der erste GC den Schwellenwert einfach auf 1, da zu diesem Zeitpunkt die Hälfte des Überlebenden 214728704 Bytes beträgt und die Gesamtzahl der Objekte mit Alter 1 315529928 Bytes beträgt, was die gewünschte Überlebensgröße überschreitet Nach diesem GC wird der Schwellenwert für ein Objekt mit Alter 1 auf Alter 1 gesetzt

这里更新了对象晋升年龄阈值为1
Desired survivor size 214728704 bytes, new threshold 1 (max 15)
- age   1:  315529928 bytes,  315529928 total
- age   2:   40956656 bytes,  356486584 total
- age   3:    8408040 bytes,  364894624 total


Hier ist eine Erklärung der Ausgabe dieser Altersverteilung:

- age   1:  315529928 bytes,  315529928 total 


- age 1Gibt die Gruppierung von Objekten mit Alter 1 an und 315529928 bytesgibt die Speichergröße an, die von Objekten mit Alter 1 belegt wird

315529928 totalDies ist ein kumulativer Wert, der die Gesamtgröße der Objekte angibt, die kleiner oder gleich dem aktuellen Gruppenalter sind. Gruppieren Sie zunächst die Objekte nach Alter. Die Gruppensumme von Alter 1 ist die Gesamtgröße von Alter 1 (die vorherigen xxx Bytes). Die Gruppensumme von Alter 2 ist die age 1 + age 2Gesamtgröße. Die Gruppensumme von Alter n ist age 1 + age 2 + ... +age ndie Gesamtgröße. Die Die Akkumulationsregeln sind in der folgenden Abbildung dargestellt.

Bild.png

Wenn der Gesamtwert der Gruppe mit der größten Gesamtsumme den Überlebenden/2 überschreitet, wird der Beförderungsschwellenwert aktualisiert.

Im zweiten GC der jungen Generation „Long Pause Young Generation GC Log“ werden die Objekte befördert, die einen GC erlebt haben, überlebt haben und noch erreichbar sind, da die neue Altersschwelle für die Beförderung 1 beträgt.

Da in diesem GC 363 Millionen Objekte hochgestuft wurden, kam es zu einer langen Pause.

denken

Ist diese „dynamische Bestimmung des Objektalters“ in JVM wirklich sinnvoll?

Persönlich denke ich, dass der Mechanismus gut ist und sich besser an die Speicherbedingungen verschiedener Programme anpassen kann, aber er ist nicht für jedes Szenario geeignet. In diesem Artikel wird es beispielsweise Probleme geben, wenn GC nicht direkt danach gestartet wird Start-up.

Da beim ersten Start des Programms das Alter der meisten Objekte 0 oder 1 beträgt, ist es leicht, eine große Anzahl überlebender Objekte mit einem Alter von 1 zu haben. Unter diesem Mechanismus zur „dynamischen Bestimmung des Objektalters“ liegt der neue Beförderungsschwellenwert auf 1 gesetzt, wodurch diese Objekte, die nicht heraufgestuft werden sollten, heraufgestuft werden

Young GC tritt beispielsweise auf, wenn das Programm verschiedene Ressourcen initialisiert und lädt. Die Ladelogik wird noch ausgeführt. Viele neu erstellte Objekte sind während dieses GC noch erreichbar.

Nach dem Durchlaufen dieses GC wird das Alter dieser Objekte auf 1 aktualisiert. Aufgrund des Einflusses des Mechanismus zur „dynamischen Bestimmung des Objektalters“ wird der Schwellenwert für das Hochstufungsalter jedoch auf das Alter der „größten Objektaltersgruppe“ aktualisiert Das heißt, dieser Stapel hat gerade einen GC erlebt. Objekte

Kurz nach diesem GC war die Ressourceninitialisierung abgeschlossen und die beteiligten zugehörigen Objekte waren wahrscheinlich nicht erreichbar. Da der Altersschwellenwert für die Beförderung jedoch gerade auf 1 aktualisiert wurde, hat dieser Stapel von Objekten im nächsten normalen Young GC ein Alter von 1 erfolgt direkt. Beförderung, Beförderung erfolgte zu früh oder aus Versehen

Lösung

Nach Durchsicht der Dokumente und Informationen stellte ich fest, dass der Mechanismus der „dynamischen Altersbestimmung“ nicht deaktiviert werden kann. Wenn Sie dieses Problem also lösen möchten, können Sie sich nur darauf verlassen, diese Berechnungsregel zu „umgehen“.

Die Bestimmung des dynamischen Alters basiert auf der Tatsache, dass die Summe der Größen aller gleichaltrigen Objekte im Survivor-Raum größer als die Hälfte des Survivor-Raums ist, sodass die Lösung basierend auf diesem Mechanismus sehr einfach ist.

Da wir unser System gut genug kennen und den ungefähren Speicherbedarf zum Laden von Ressourcen kennen, können wir als Kapazität des Überlebenden einen Wert festlegen, der größer als die Summe dieser temporär erreichbaren Objekte ist.

Im obigen Protokoll hat beispielsweise das Objekt mit Alter 1 nach dem ersten GC 315529928 Bytes (300 MB) und die gewünschte Überlebensgröße beträgt (Überlebensgröße / 2) 214728704 Bytes (204 MB). Dann kann der Überlebende auf mehr eingestellt werden als 600 Mio.

Passen Sie jedoch aus Sicherheitsgründen den Überlebenden auf 800 Millionen an, sodass die gewünschte Überlebendengröße etwa 400 Millionen beträgt. Nach dem ersten Young GC wird der Altersschwellenwert für die Beförderung nicht aktualisiert, da die Summe der Objekte mit Alter 1 den gewünschten Überlebenden überschreitet Größe. , sodass es keine langen GC-Pause-Probleme gibt, die durch frühe/falsche Beförderung verursacht werden

Der Überlebende kann die Größe nicht direkt angeben, aber die Größe des Überlebenden kann durch Anpassen des Verhältnisses -XX:SurvivorRatio angepasst werden.

-XX:SurvivorRatio=8


Gibt das Verhältnis der beiden Survivor- und Edgen-Bereiche an. 8 bedeutet zwei Survivor:Eden=2:8, das heißt, ein Survivor macht 1/10 der neuen Generation aus.

Die Berechnungsmethode ist:
CleanShot 2023-12-08 um 09.24.23@2x.png

Etwas transformiert lautet die Formel zur Berechnung der Größe von Eden:

CleanShot 2023-12-08 um 09.28.35@2x.png

Hier wird ein gestapeltes Histogramm verwendet, um das Raumverhältnis von Eden/Survivor unter verschiedenen SurvivorRatio-Werten im Detail zu erklären:

Bild.png

Okay, jetzt zwingen wir den Survivor direkt durch das Verhältnis zu einer Vergrößerung.

-XX:SurvivorRatio=3


Nach der Anpassung beträgt das gesamte Überlebensverhältnis 40 % und die Größe 1717829632 Bytes. Die Hälfte eines einzelnen S0/S1 macht ebenfalls 10 % aus – 429457408 Bytes, was weit mehr ist als die Gesamtgruppengröße von age=1 von 315529928 Bytes.

Auf diese Weise machen die nach Young GC (maximale Altersgruppe) nach Survivor kopierten Objekte nicht 50 % des Gesamtanteils aus und MaxTenuringThreshold wird nicht auf 1 aktualisiert, was dieses Problem der „zufälligen Beförderung“ natürlich löst. Frage**

Nachdem die Änderung abgeschlossen und beendet wurde, die Version erneut freigegeben und manuell vorgeheizt wurde, besteht das Problem der langen Pause nach dem Schneiden nicht mehr. Young GC ist stabil bei 30-100 ms, was erfolgreich gelöst wurde!

Expandieren

Warum ist der Aufstieg auf 300M um ein Vielfaches langsamer als die Wiedereinführung von 3G in der jungen Generation?

Gemäß den Merkmalen des Kopieralgorithmus hängt der Zeitverbrauch des Kopieralgorithmus hauptsächlich von der Größe der überlebenden Objekte und nicht von der Größe des Gesamtraums ab.

In der 4G-Junggeneration oben (eigentlich ist nur Eden + S0 verfügbar) müssen Sie beispielsweise während der GC nur den Objektgraphen beginnend mit GC ROOTS durchlaufen und die erreichbaren Objekte nach S1 kopieren. Es ist nicht erforderlich, den gesamten Objektgraphen zu durchlaufen junge Generation.

Eine ausführliche Einführung in den Kopieralgorithmus finden Sie in meinem anderen Artikel „Implementierung des Garbage Collection-Algorithmus – Kopieralgorithmus (vollständiger ausführbarer C-Sprachcode)“.

Im obigen GC-Protokoll mit langer Pause fanden 363 Mio. Werbeaktionen und etwa 300 Mio. Recyclingvorgänge statt. Im Vergleich zum ersten GC kann grundsätzlich gefolgert werden, dass es sich bei den ausgegebenen 1,5 S im Wesentlichen um Werbeaktionen handelt.

Warum ist die Werbeaktion so zeitaufwändig?

Schließlich beinhaltet die Förderung das generationsübergreifende Kopieren (tatsächlich sind sowohl die junge als auch die alte Generation Haufen, und es gibt im Wesentlichen keinen Unterschied beim Kopieren. Sie sind alle nur Memcpy, aber es muss zusätzlich mehr Logik verarbeitet werden.)

Die zu verarbeitende Logik wird komplexer sein, z. B. Zeigeraktualisierung und andere Vorgänge, was zeitaufwändiger ist. Ist das verständlich?

Native Code-Emulation

Hier ist auch ein Codestück beigefügt, mit dem das Problem lokal simuliert werden kann. Der Test kann direkt unter Oracle JDK7 ausgeführt werden.

//jdk7.。

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class PromotionTest {
    public static void main(String[] args) throws IOException {
        //模拟初始化资源场景
        List<Object> dataList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            dataList.add(new InnerObject());
        }
        //模拟流量进入场景
        for (int i = 0; i < 73; i++) {
            if(i == 72){
                System.out.println("Execute young gc...Adjust promotion threshold to 1");
            }
            new InnerObject();
        }
        System.out.println("Execute full gc...dataList has been promoted to cms old space");
        //这里注意dataList中的对象在这次Full GC后会进入老年代
        System.gc();
    }
    public static byte[] createData(){
        int dataSize = 1024*1024*4;//4m
        byte[] data = new byte[dataSize];
        for (int j = 0; j < dataSize; j++) {
            data[j] = 1;
        }
        return data;
    }
    static class InnerObject{
        private Object data;

        public InnerObject() {
            this.data = createData();
        }
    }
}


JVM-Optionen

-server -Xmn400M -XX:SurvivorRatio=9 -Xms1000M -Xmx1000M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+PrintGCApplicationStoppedTime -XX:+UseConcMarkSweepGC


Beachten Sie, dass die Erläuterungen zu Garbage Collection-bezogenen Mechanismen in diesem Artikel alle auf HotSpot JVM, Parallel New + CMS Old, basieren.

Referenz

Autor: JD Insurance Jiang Xin

Quelle: JD Cloud Developer Community Bitte geben Sie beim Nachdruck die Quelle an

IntelliJ IDEA 2023.3 & JetBrains Family Bucket jährliches Hauptversions-Update neues Konzept „defensive Programmierung“: Machen Sie sich einen stabilen Job GitHub.com betreibt mehr als 1.200 MySQL-Hosts, wie kann man nahtlos auf 8.0 aktualisieren? Das Web3-Team von Stephen Chow wird nächsten Monat eine unabhängige App starten. Wird Firefox eliminiert? Visual Studio Code 1.85 veröffentlicht, schwebendes Fenster Yu Chengdong: Huawei wird nächstes Jahr bahnbrechende Produkte auf den Markt bringen und die Geschichte der Branche neu schreiben. Die US-amerikanische CISA empfiehlt den Verzicht auf C/C++, um Schwachstellen in der Speichersicherheit zu beseitigen. TIOBE Dezember: C# soll zur Programmierung werden Sprache des Jahres. Ein von Lei Jun vor 30 Jahren verfasster Artikel: „Prinzip und Design des Expertensystems zur Computervirenbestimmung“
{{o.name}}
{{m.name}}

Supongo que te gusta

Origin my.oschina.net/u/4090830/blog/10320978
Recomendado
Clasificación