[JAVA] TOMCAT Mordfall

OOM Killers mörderische Absicht

Da der Kunde EC2 Tomcat am frühen Morgen plötzlich von OOM Killer ohne Zugang getötet wurde, denke ich, dass einige Leute möglicherweise mit dem gleichen Problem wie die Untersuchung konfrontiert sind, daher werde ich es als Memorandum schreiben. .. Ich denke, es wird ein wenig nützlich sein, um den Java-Heap zu optimieren. (Dieser Artikel ist für Java 8)

OOM Killer ist eine Standard-Linux-Funktion, die Prozesse stoppt, die Speicher verbrauchen, um den gesamten Server bei Speichermangel zu schützen. Tomcat wurde plötzlich vom OOM Killer getötet. Es ist ein Zustand, in dem Tsuji ohne Fragen aufgeschlitzt wird. Da es früher Morgen ist, wird nichts im Zugriffsprotokoll und in Catalina.out aufgezeichnet

OpenJDK 64-Bit Server VM warning: Setting LargePageSizeInBytes has no effect on this OS. Large page size is 2048K.
OpenJDK 64-Bit Server VM warning: Failed to reserve large pages memory req_addr: 0x00000006c0000000 bytes: 4294967296 (errno = 12)

OOM Killer wurde sofort ausgeführt, nachdem das Protokoll aufgezeichnet wurde. Eh großer Seitenspeicher ~ ??? Bytes: 4294967296 ~ ??? Schauen wir uns vorerst die Erinnerung mit dem freien Befehl an, ohne die Situation zu verschlucken. Da es nach dem Neustart ist, gibt es natürlich viel freien Speicher. Eine der erstaunlichen Tatsachen wurde jedoch enthüllt. Ist Swap nicht insgesamt 0? Dies ist die AWS-Standardeinstellung. Da es sich um einen elastischen Server handelt, ist Swap noch nicht cool. Ich denke, es geht darum, physischen Speicher zu verwenden. Da der Kunde jedoch keinen elastischen Vertrag hatte, wurde OOM Killer verrückt und schlug Tsuji auf.

Überprüfen Sie die Java-Umgebungsvariablen, um die Wahrheit über Tsuji Sword zu erfahren

Ich verstehe eine der Ursachen für Tsuji-Hiebe, aber ich konnte den wichtigen Grund, warum es zu wenig Gedächtnis gab, nicht lösen. Verwenden Sie vorerst den Befehl TOP, um die Speichernutzung in absteigender Reihenfolge zu sortieren und die laufenden Prozesse zu überprüfen. Daher verbrauchten Virenprüf- und Protokollsammler-Tools außer Java 1,6 GB Arbeitsspeicher. Notieren Sie sich an dieser Stelle die Java-PID.

Da es sicher scheint, dass die Speicherzuweisung aufgrund von (errno = 12) fehlgeschlagen ist, dachte ich, dass mit den Anfangseinstellungen etwas nicht stimmt. Überprüfen Sie zunächst die Einstellungen der Umgebungsvariablen. Öffnen Sie setenv.sh und überprüfen Sie.

-XX:NewSize=1024 m size ・ ・ ・ ・ ・ ・ ・ ・ Mindestgröße des neuen Bereichs
-XX:MaxNewSize=1024 m ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ Maximale Größe des neuen Bereichs
-Xms4096 size ・ hea p ・. ・ ・ ・ ・ ・ ・ ・ Mindestgröße des gesamten Java-Heaps
-Xmx4096 size ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ Maximale Größe des gesamten Java-Heaps
-XX:SurvivorRatio=12 ratio ・ ・ ・ Surv area ・ ・ ・ Verteilungsverhältnis im neuen Gebiet: 12 für das Gebiet der Überlebenden 1
-XX:TargetSurvivorRatio=90 ・ ・ ・ Minor GC, wenn die Überlebensfläche 90% erreicht
-XX:MaxTenuringThreshold=15 ・ ・ ・ Anzahl der Bewegungen im Bereich Überlebende
-XX:LargePageSizeInBytes=256 m space ・ Sicherer Speicherplatz für die Verwendung großer Seiten
-XX:MetaspaceSize=384 m space ・ ・ ・ ・ Meta-Raumgröße
-XX:MaxMetaspaceSize=384 m ・ ・ ・ Maximaler Metaraum

Es scheint, dass der Middleware-Anbieter es zurückgesetzt hat und der Java-Heap gegenüber der Standardeinstellung von Tomcat erheblich erhöht wurde. Ja, es gibt eine LargePage-Einstellung, aber 256 MB. Überprüfen Sie auch den Java-Heap mit jmap.

# jmap -heap [PID]

Sie können die Details von Java Heap überprüfen, indem Sie die Java-Prozessnummer in [PID] eingeben. Wenn Sie sich jmap -heap ansehen, sehen Sie die Standardeinstellungen, die nicht von setenv definiert wurden, sodass Sie das gesamte Bild sehen können. Undefiniert in setenv

-XX:CompressedclassspaceSize=1024m ・ ・ ・ Standardwert des komprimierten Klassenbereichs

Ich habe das gefunden. Die Größe des gesamten Java-Heaps wird mit -Xms -Xmx festgelegt, sodass Sie sehen können, dass es sich um 4 GB handelt. Der NEUE Bereich zum Speichern neuer Objekte ist auf -XX: NewSize mit 1 GB eingestellt. Das neue Gebiet ist in Eden und Survivor unterteilt. -XX: Es gibt TargetSurvivorRaio, ein Vielfaches, das den Eden-Bereich für Survivor-Bereich 1 bestimmt. Mit anderen Worten, Sie können sehen, dass das Verhältnis von Überlebenden 1 und Eden 12 im Bereich 1 GB NEU beträgt. Im obigen Fall wird der alte Bereich für den gesamten Java-Heap auf 4 GB und der neue Bereich auf 1 GB festgelegt, sodass die verbleibenden 3 GB der alte Bereich sind.

Überprüfung über Java-Heap

Lassen Sie uns nun ein wenig über den Java-Heap sprechen. Der Java-Heap besteht aus einem neuen und einem alten Bereich. Das neue Gebiet ist weiter unterteilt in das Gebiet Eden und das Gebiet Survivor. Der Survivor-Bereich besteht weiterhin aus Survivor0 und Survivor1.

Neu erzeugte Objekte betreten zuerst Eden. Wenn das Eden voll ist, wird die Minor Garbage Collection (GC) aktiviert und überprüft, um die lebenden an Survivor0 zu übertragen. Wenn Eden voll ist, startet der kleinere GC und dieses Mal werden die überlebenden Objekte zu Survivor1 verschoben. Zu diesem Zeitpunkt ist Survivor 0 leer. Während die überlebenden Objekte wiederholt mit Survivor 0 und 1 gesiebt werden, wird die Bewegung so oft wiederholt, wie durch -XX: MaxTenuringThreshold definiert. Selbst wenn es zwei Survivor-Bereiche gibt, ist einer von ihnen immer leer, sodass er als ein Feld angesehen werden kann. Und -XX: MaxTenuringThreshold Ein mutiger Mann, der die Anzahl der GCs überlebt hat? Zieht als alter Soldat in die Altstadt.

Wenn der alte Bereich endlich voll ist, wird ein vollständiger GC angewendet, um den Speicher zu bereinigen. Tatsächlich ist der Java-Heap nicht der einzige Speicherbereich für JVMs. Es kann grob in einen Java-Heap und einen nativen Heap unterteilt werden. Der native Heap besteht aus MetaSpace, CompressedClassSpace, C Heap und Thread Stack.

Es ist ein wenig schwer zu verstehen, deshalb werde ich es in einer Kugel zusammenfassen.

Die folgende Abbildung zeigt eine Karte dieser Speicherzuordnungen. Ich habe auch die Parameter der oben genannten Umgebungsvariablen angewendet. Es scheint, dass ConpressedClassSpace standardmäßig 1 GB reserviert, sodass der native Heap allein fast 2 GB einschließlich anderer Teile verbraucht.

image.png

Der Speicher ist in der Anfangseinstellungsphase voll. In der Phase, in der der vollständige GC gestartet wird, ist der Speicher fast voll. Es scheint also, dass der vollständige GC gewonnen oder OOME gewonnen hat oder sich in einem Kampfzustand von Iwaryujima befand. Es ist wahrscheinlich, dass die alten Soldaten, die sich zum Zeitpunkt der vollständigen GC im alten Gebiet angesammelt hatten, rebellierten und ein Out of Memory-Fehler auftrat.

Fragen Sie bei jstat nach

Jstat ist eine schnelle Möglichkeit, den tatsächlichen Java-Heap in Echtzeit anzuzeigen. Es ist schwierig, alle zu verstehen, da es verschiedene Optionen gibt, aber die folgenden beiden sind für uns Anfänger leicht zu verstehen.

# jstat -gc -t [PID] 1000
# jstat -gcutil -t [PID] 1000 

-t hat am Anfang einen Zeitstempel und bedeutet, dass die Informationen der Ziel-PID alle 1000 Millisekunden ausgegeben werden. -gc gibt die Nutzungsmenge jedes Elements in KB-Einheiten aus. -gcutil zeigt die Verwendung jedes Elements in% an. Jedes Ausgabeelement ist wie folgt.

Liste der Elemente für jstat -gc

Alle Werte vor CCSU sind KB

Artikelname Artikelinhalt
S0C Survivor0-Einstellung
S1C Survivor1-Einstellungen
S0U Tatsächliche Verwendung von Survivor0
S1U Tatsächliche Nutzung von Survivor1
EC Eden Einstellwert
EU Tatsächliche Nutzung von Eden
OC Alte Gebietseinstellung
OU Tatsächliche Verwendung von Alt
MC Metaspace-Einstellung
MU Tatsächliche Verwendung von Metaspace
CCSC Stellen Sie den Wert des komprimierten Klassenbereichs ein
CCSU Tatsächliche Nutzung des komprimierten Klassenbereichs
YGC Anzahl der Garbage Collection-Ereignisse der jungen Generation
YGCT Kumulative Zeit der Müllabfuhr der jungen Generation
FGC Anzahl der Ereignisse in der vollständigen Speicherbereinigung
FGCT Kumulative Zeit der vollständigen Speicherbereinigung
GCT Gesamtzeit der Speicherbereinigung

Liste der Elemente für jstat -gcutil

Die Einheit vor CCS ist%

Artikelname Artikelinhalt
S0 Tatsächliche Nutzungsrate von Survivor0
S1 Tatsächliche Nutzungsrate von Survivor1
E Tatsächliche Nutzungsrate von Eden
O Tatsächliche Nutzungsrate von Alt
M Tatsächliche Nutzungsrate von Metaspace
CCS Tatsächliche Nutzung des komprimierten Klassenbereichs
YGC Anzahl der Garbage Collection-Ereignisse der jungen Generation
YGCT Kumulative Zeit der Müllabfuhr der jungen Generation
FGC Anzahl der Ereignisse in der vollständigen Speicherbereinigung
FGCT Kumulative Zeit der vollständigen Speicherbereinigung
GCT Gesamtzeit der Speicherbereinigung

Wenn Sie mit jstat überwachen, können Sie den Moment sehen, in dem der Minor GC ausgeführt wird und der Surviver-Bereich neu geschrieben wird, wenn Eden wirklich 100% ist. Wenn Eden hier fest auf 0 gesetzt ist und die Zeit pro Neben-GC 0,1 Sekunden oder weniger beträgt, gibt es meines Erachtens kein Problem mit dem neuen Bereich. (Wenn nicht, bitte darauf hinweisen ...) Andererseits frage ich mich in dieser Zeit, ob es notwendig ist, dreimal so viel Speicherplatz wie Old mit 1 GB für NEW einzunehmen. Es ist eine Unsinnsgeschichte, dass die vollständige GC nicht für immer ausgeführt wird und sich am Ende Zombies ansammeln und von OOM Killer getötet werden. Wenn der neue Bereich so groß ist, sollte der alte ungefähr gleich sein.

Fazit

Die richtigen Einstellungen können schwerwiegend sein. Seien wir vorsichtig.

Es gab mehr Leute, die mehr auf Lager hatten, als ich mir vorgestellt hatte, deshalb werde ich die Schlussfolgerung ein wenig ergänzen. Wie ich im Kommentarbereich geschrieben habe, ist die direkte Ursache des Fehlers 8 GB Speicher, was der Einstellung von 32 GB Speicher entspricht, die der Lieferant in der Vergangenheit bereitgestellt hat. Es ist wahrscheinlich, dass es auf den Server von angewendet wurde. Infolgedessen schwollen der Java-Heap, der native Heap und der Gesamtwert der anderen Speichernutzung auf das gleiche Niveau wie der physische Speicher an und liefen zum Zeitpunkt der vollständigen GC über. Wenn Sie beispielsweise wie dieses Mal mit 8 GB arbeiten, wird empfohlen, Folgendes als Richtlinie festzulegen und es sorgfältig zu optimieren, während Sie die Situation beobachten.

-XX:NewSize=700m 
-XX:MaxNewSize=700m 
-Xms2048m 
-Xmx2048m 
-XX:MetaspaceSize=300m
-XX:MaxMetaspaceSize=300m 
-XX:SurvivorRatio=6 
-XX:TargetSurvivorRatio=9 
-XX:MaxTenuringThreshold=15

(Das Obige ist eine Anleitung, auch wenn Sie es satt haben. Stellen Sie sicher, dass Sie es unabhängig voneinander einstellen. Die Bedingungen sind je nach Umgebung der Betriebsanwendung und des Servers völlig unterschiedlich. Sie müssen daher Einstellungen vornehmen, die für jede Umgebung geeignet sind. Wie lautet die obige Einstellung der vollständigen Kopie? Bitte beachten Sie, dass enoshiman keine Verantwortung übernehmen kann, auch wenn es ein Problem gibt.) Wenn die Einstellung zu diesem Zeitpunkt offensichtlich falsch ist, können Sie sie ziemlich schnell verstehen. In der Realität kann es jedoch verschiedene Faktoren geben, z. B. einen Speicherverlust oder unzureichenden physischen Speicher aufgrund der Anwendungsseite. Daher Heap-Dump für einen bestimmten Zeitraum. Es ist auch effektiv zu überprüfen, ob abnormale Zahlen vorhanden sind, oder mithilfe von VisualVM zu visualisieren, wie in den Kommentaren angegeben. Sobald Sie sich daran gewöhnt haben, können Sie die Änderung sehen, sodass Sie sie auch mit jstat übernehmen können. Betrachtet man den Übergang der tatsächlichen Nutzungsmenge jedes Bereichs und der GC-Zeit, so dauert es möglicherweise schlecht, wenn die Abnahme schlecht ist, der GC lange dauert oder wenn Unebenheiten vorliegen, stimmt möglicherweise etwas nicht. Wenn ich mir den GC ansehe, der richtig funktioniert, fühle ich mich ein wenig krank. (Es scheint ein seltsames Hobby zu sein ...)

Recommended Posts

TOMCAT Mordfall
Katerfehler