[JAVA] Affaire de meurtre TOMCAT

L'intention meurtrière d'OOM Killer

Puisque le client EC2 Tomcat a été soudainement tué par OOM Killer tôt le matin sans accès, je pense que certaines personnes peuvent être confrontées au même problème que l'enquête, donc je vais l'écrire sous forme de mémorandum. .. Je pense que ce sera un peu utile pour régler le tas Java. (Cet article concerne Java 8)

OOM Killer est une fonction Linux standard qui arrête les processus qui consomment de la mémoire pour protéger l'ensemble du serveur en cas de manque de mémoire. Tomcat a été soudainement tué par l'OOM Killer. C'est un état de Tsuji coupant sans poser de questions. Comme il est tôt le matin, rien n'est enregistré dans le journal d'accès, et catalina.out

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 était en cours d'exécution immédiatement après l'enregistrement du journal. Eh mémoire de grandes pages ~ ??? octets: 4294967296 ~ ??? Sans avaler la situation, regardons la mémoire avec la commande free pour le moment. Comme c'est après le redémarrage, il y a naturellement beaucoup de mémoire libre. Cependant, l'un des faits étonnants a été révélé. Le swap total n'est-il pas 0? Il s'agit du paramètre par défaut d'AWS. Comme il s'agit d'un serveur élastique, le swap n'est pas encore quelque chose de cool, donc je suppose qu'il s'agit d'utiliser la mémoire physique, mais comme le client n'avait pas de contrat élastique, OOM Killer est devenu fou et a continué à couper Tsuji.

Vérifiez les variables d'environnement Java pour révéler la vérité sur Tsuji Sword

Je comprends l'une des causes du slashing de Tsuji, mais je n'ai pas été en mesure de résoudre la raison importante pour laquelle il est devenu Out of Memory. Pour le moment, utilisez la commande TOP pour trier l'utilisation de la mémoire par ordre décroissant et vérifier les processus en cours. En conséquence, à part Java, les agents antivirus et les agents des outils de collecte de journaux ont consommé 1,6 Go de mémoire. À ce stade, notez le PID Java.

Puisqu'il semble certain que l'allocation de mémoire a échoué à cause de (errno = 12), j'ai pensé qu'il y avait un problème avec les paramètres initiaux. Commencez par vérifier les paramètres de la variable d'environnement. Ouvrez setenv.sh et vérifiez.

-XX:NewSize=1024m ・ ・ ・ ・ ・ ・ ・ ・ ・ Taille minimale de la nouvelle zone
-XX:MaxNewSize=1024m ・ ・ ・ ・ ・ ・ ・ ・ ・ Taille maximale de la nouvelle zone
-Xms4096 ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ Taille minimale de l'ensemble du tas Java
-Xmx4096 ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ Taille maximale de l'ensemble du tas Java
-XX:SurvivorRatio=12 ・ ・ ・ ・ ・ ・ ・ ・ ・ Ratio de distribution dans la nouvelle zone: 12 pour la zone Survivor 1
-XX:TargetSurvivorRatio=90 ・ ・ ・ GC mineur lorsque la zone du survivant atteint 90%
-XX:MaxTenuringThreshold=15 ・ ・ ・ Nombre de mouvements dans la zone Survivor
-XX:LargePageSizeInBytes=256m ・ ・ Espace sécurisé pour l'utilisation de grandes pages
-XX:MetaspaceSize=384m ・ ・ ・ ・ ・ Taille de l'espace méta
-XX:MaxMetaspaceSize=384m ・ ・ ・ Espace méta maximum

Il semble que le fournisseur du middleware l'a réinitialisé et que le tas Java a été considérablement augmenté par rapport au paramètre par défaut de Tomcat. Bien sûr, il existe un paramètre LargePage, mais il est de 256 Mo. Vérifiez également le tas Java avec jmap.

# jmap -heap [PID]

Vous pouvez vérifier les détails de Java Heap en entrant le numéro de processus Java dans [PID]. Si vous regardez jmap -heap, vous pouvez voir les paramètres par défaut qui ne sont pas définis par setenv, donc vous pouvez voir l'image entière. Indéfini dans setenv

-XX:CompressedclassspaceSize=1024m ・ ・ ・ Valeur par défaut de la zone de classe compressée

Je l'ai trouvé. La taille de l'ensemble du tas Java est définie avec -Xms -Xmx, vous pouvez donc voir qu'elle est de 4 Go. La nouvelle zone de stockage de nouveaux objets est définie sur -XX: NewSize avec 1 Go. La nouvelle zone est divisée en Eden et Survivor. -XX: Il y a TargetSurvivorRaio, qui est un multiple qui détermine la zone Eden pour la zone Survivor 1. En d'autres termes, vous pouvez voir que le ratio de Survivor est de 1 et Eden est de 12 dans la nouvelle zone de 1 Go. Dans le cas ci-dessus, l'ancienne zone est définie sur 4 Go pour l'ensemble du segment Java et la nouvelle zone est de 1 Go, les 3 Go restants sont donc l'ancienne zone.

Avis sur Java Heap

Passons maintenant en revue un peu le tas Java. Le tas Java se compose d'une nouvelle zone et d'une ancienne zone. La nouvelle zone est divisée en deux zones: Eden et Survivor. La zone Survivor comprend en outre Survivor0 et Survivor1.

Les objets nouvellement générés entrent d'abord dans l'Eden. Lorsque l'Eden est plein, la collecte des ordures mineures (GC) sera activée et filtrée pour transférer les vivants vers Survivor0. De plus, quand Eden est plein, le GC mineur démarre et cette fois déplace les objets survivants vers Survivor1. À ce stade, Survivor 0 est vide. Lors du tamisage répété des objets survivants avec Survivor 0 et 1, le mouvement est répété autant de fois que défini par -XX: MaxTenuringThreshold. Par conséquent, même s'il y a deux zones Survivor, l'une d'elles est toujours vide, elle peut donc être considérée comme un espace. Et -XX: MaxTenuringThreshold Un homme courageux qui a survécu au nombre de GC? Déménage dans la vieille région en tant que vieux soldat.

Lorsque l'ancienne zone est enfin pleine, le GC complet est appliqué pour nettoyer la mémoire. En fait, le tas Java n'est pas la seule zone de mémoire pour les JVM. Il peut être grossièrement divisé en un tas Java et un tas natif. Le tas natif se compose de MetaSpace, CompressedClassSpace, C Heap et Thread Stack.

C'est un peu difficile à comprendre, alors je vais le résumer dans une puce.

--Java tas --Nouvelle zone ・ ・ ・ Zone pour mettre un nouvel objet --Eden ・ ・ ・ Zone pour mettre un nouvel objet --Survivant ・ ・ ・ Zone pour mettre les objets qui ont survécu dans GC (Survivor0, Survivor1)

La figure ci-dessous est une carte de ces allocations de mémoire. J'ai également appliqué les paramètres des variables d'environnement ci-dessus. Il semble que ConpressedClassSpace réserve 1 Go par défaut, il semble donc que le tas natif à lui seul consomme près de 2 Go, y compris les autres parties.

image.png

La mémoire est pleine au stade du réglage initial. Au stade où le GC complet démarre, la mémoire est presque pleine, il semble donc que le GC complet a gagné ou OOME a gagné ou était dans un état de bataille d'Iwaryujima. Il est probable que les vieux soldats qui s'étaient accumulés dans la vieille région au moment du GC complet se sont rebellés et une erreur de mémoire insuffisante s'est produite.

Vérifier avec jstat

Jstat est un moyen rapide de voir le tas Java réel en temps réel. Il est difficile de tous les comprendre car il existe différentes options, mais les deux suivantes sont faciles à comprendre pour nous, débutants.

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

-t a un horodatage au début et signifie que les informations du PID cible sont sorties toutes les 1000 millisecondes. -gc renvoie la quantité d'utilisation de chaque élément en unités Ko. -gcutil affiche l'utilisation de chaque élément en%. Chaque élément de sortie est comme suit.

Liste des éléments pour jstat -gc

Toutes les valeurs avant CCSU sont KB

nom de l'article Contenu de l'article
S0C Paramètre Survivor0
S1C Paramètres de Survivor1
S0U Utilisation réelle de Survivor0
S1U Utilisation réelle de Survivor1
EC Valeur de réglage Eden
EU Utilisation réelle d'Eden
OC Ancien cadre de zone
OU Utilisation réelle de l'ancien
MC Paramètre Metaspace
MU Utilisation réelle de metaspace
CCSC Définir la valeur de la zone de classe compressée
CCSU Utilisation réelle de la zone de classe compressée
YGC Nombre d'événements de collecte des ordures de la jeune génération
YGCT Temps cumulé du ramassage des ordures de la jeune génération
FGC Nombre d'événements dans le garbage collection complet
FGCT Temps cumulé de récupération de place complète
GCT Durée totale cumulée du ramasse-miettes

Liste des éléments pour jstat -gcutil

L'unité avant CCS est%

nom de l'article Contenu de l'article
S0 Taux d'utilisation réel de Survivor0
S1 Taux d'utilisation réel de Survivor1
E Taux d'utilisation réel d'Eden
O Taux d'utilisation réel de l'ancien
M Taux d'utilisation réel de la méta-espace
CCS Utilisation réelle de la zone de classe compressée
YGC Nombre d'événements de collecte des ordures de la jeune génération
YGCT Temps cumulé du ramassage des ordures de la jeune génération
FGC Nombre d'événements dans le garbage collection complet
FGCT Temps cumulé de récupération de place complète
GCT Durée totale cumulée du ramasse-miettes

Si vous surveillez avec jstat, vous pouvez voir le moment où le GC mineur est exécuté et la zone Surviver est réécrite lorsque Eden est vraiment à 100%. Si Eden est fermement mis à 0 ici et que le temps par GC mineur est de 0,1 seconde ou moins, je pense qu'il n'y a pas de problème avec la nouvelle zone. (Sinon, veuillez le préciser ...) Par contre, à cette époque, je me demande s'il faut prendre 3 fois plus d'espace que l'ancien avec 1 Go pour le NOUVEAU. C'est une histoire absurde que le GC complet ne sera pas exécuté pour toujours et que les zombies s'accumuleront et seront tués par OOM Killer à la fin. Si la nouvelle zone est si grande, je pense que l'ancienne devrait être à peu près la même.

Conclusion

Des réglages appropriés peuvent être fatals. Soyons prudents.

Il y avait plus de gens qui ont stocké plus que je ne l'imaginais, je vais donc compléter un peu la conclusion. Tout d'abord, comme je l'ai écrit dans la section des commentaires, la cause directe de l'échec est 8 Go de mémoire, ce qui est le même que le réglage de 32 Go de mémoire fourni par le fournisseur dans le passé. Il est probable qu'il ait été appliqué au serveur de. En conséquence, le tas Java, le tas natif et la valeur totale des autres utilisations de la mémoire ont gonflé au même niveau que la mémoire physique et ont débordé au moment du GC complet. Par exemple, lors d'un fonctionnement au niveau de 8 Go comme cette fois, il est recommandé de définir ce qui suit comme guide et de le régler avec diligence tout en observant la situation.

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

(Ce qui précède est un guide même si vous en avez assez. Assurez-vous de le régler indépendamment. Les conditions sont complètement différentes en fonction de l'environnement de l'application ou du serveur d'exploitation, vous devez donc définir les paramètres qui conviennent à chaque environnement. Quel est le paramètre ci-dessus pour la copie complète? Veuillez noter qu'enoshiman ne peut assumer aucune responsabilité même en cas de problème.) Si le paramètre est manifestement faux comme cette fois, vous pouvez le dire immédiatement, mais en réalité, il peut y avoir divers facteurs tels qu'une fuite de mémoire ou une mémoire physique insuffisante en raison du côté application, donc un vidage de tas pendant un certain temps. Il est également efficace de vérifier s'il existe des nombres anormaux, ou de visualiser à l'aide de VisualVM comme indiqué dans les commentaires. Une fois que vous vous y êtes habitué, vous pouvez voir le changement afin de pouvoir le récupérer même avec jstat. En regardant la transition de la quantité d'utilisation réelle de chaque zone et du temps GC, si la diminution est mauvaise, si le GC prend beaucoup de temps ou s'il y a des inégalités, il se peut qu'il y ait quelque chose qui ne va pas. Regarder le GC qui fonctionne correctement me rend un peu malade. (Cela semble être un passe-temps étrange ...)

Recommended Posts

Affaire de meurtre TOMCAT
erreur Tomcat