web-dev-qa-db-fra.com

java8 "Java.lang.OutOfMemoryError: métaspace"

Après avoir basculé notre application Java (services exécutés sur Tomcat) JRE de Java 7 à Java 8, nous avons commencé à voir Java.lang.OutOfMemoryError: Metaspace après quelques jours d'exécution avec un volume de trafic élevé. 

L'utilisation du tas était correcte. Le méta-espace saute après le moment où le même flux de code a été exécuté lors des tests de performances.

Quelles pourraient être les causes possibles du problème de mémoire métaspace?

Les paramètres actuels sont:

-server -Xms8g -Xmx8g -XX:MaxMetaspaceSize=3200m  -XX:+UseParNewGC 
-XX:+UseConcMarkSweepGC -XX:MaxGCPauseMillis=1000 
-XX:+DisableExplicitGC -XX:+PrintGCDetails 
-XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=7 -XX:NewSize=5004m 
-XX:MaxNewSize=5004m -XX:MaxTenuringThreshold=12 
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintFlagsFinal  
-XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution 
-XX:+PrintGCCause -XX:+PrintAdaptiveSizePolicy 
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=3 -XX:GCLogFileSize=200M 

En outre, l'application utilise beaucoup de réflexion. Nous utilisons également un chargeur de classes personnalisé. Tous fonctionnaient bien dans Java 7.

20
Jane

Je suppose que vous pouvez créer le problème avec la même demande (ensemble de demandes) sur une période donnée ... C'est une bonne chose que vous ayez défini MaxMetaspaceSize, sinon l'application utilisera la mémoire native jusqu'à épuisement de sa capacité ... .. Mais je vais commencer par les étapes suivantes:

  1. Vérifiez si le nombre de classes chargées dans la machine virtuelle Java ne cesse de croître pour la même demande lorsque vous l'envoyez plusieurs fois au serveur. Si c'est le cas, vous créez peut-être des classes dynamiques susceptibles d'entraîner un accroissement du nombre de classes chargées dans le métaspace. Bien comment vérifier le nombre de classes chargées, vous pouvez utiliser visualvm pour vous connecter au serveur à l'aide de JMX ou exécuter localement pour simuler. Je mentionnerai les étapes pour local, mais pour la connexion distante JMX, vous devez ajouter les paramètres suivants aux applications de la machine virtuelle Java, puis le démarrer et se connecter à distance sur le port 9999 et avec -XX: + UnlockDiagnosticVMOptions.
   -Dcom.Sun.management.jmxremote -Dcom.Sun.management.jmxremote.port=9999 -Dcom.Sun.management.jmxremote.authenticate=false -Dcom.Sun.management.jmxremote.ssl=false -XX:+UnlockDiagnosticVMOptions

Une fois que vous avez connecté visualvm (jvisualvm) à la machine virtuelle Java, cliquez sur le moniteur, puis voyez le nombre de classes chargées. Là, vous pouvez surveiller le tas ainsi que le méta-espace. Mais je vais ajouter les autres outils pour surveiller de près le métaspace.

  1. De même, une fois que vous êtes connecté à jvm, vous pouvez prendre un instantané de tas et connaître les classes chargées à l'aide d'OQL. Par conséquent, avant de procéder à un vidage de segment de mémoire, arrêtez les demandes adressées au serveur afin de ne pas intercepter les demandes en cours/le code en cours d’exécution et les objets associés, mais cela n’est pas nécessaire. Ainsi, après avoir exécuté plusieurs fois le même ensemble de demandes, dans visualvm, dans l'espace "moniteur", cliquez sur "Dépôt de tas" dans le coin supérieur droit ". Ouvrez/chargez l'instantané et vous verrez l'option de la console OQL. Et vous verrez certaines requêtes OQL prédéfinies sur le panneau inférieur droit sous analyse permgen. Exécutez la requête nommée "histogramme chargé de classe chargé", je suppose que cela donnera le nombre de classes chargées par chaque chargeur de classe. Vous pouvez l'utiliser pour déterminer quel chargeur de classe est en cours de chargement.

sélectionnez map (sort (map (heap.objects ('Java.lang.ClassLoader'), '' chargeur: it, compte: it.classes.elementCount} '),' lhs.count <rhs.count ') , 'toHtml (it) + "
"')

Mais la requête ci-dessus nommée "classe chargée par le chargeur de classe" sera lente, ce qui affichera les classes chargées par chaque chargeur de classe.

select { loader: cl,
             classes: filter(map(cl.classes.elementData, 'it'), 'it != null') }
    from instanceof Java.lang.ClassLoader cl
  1. Ensuite, essayez de retracer la croissance dans la zone métaspace. Nous allons maintenant utiliser jconsole et quelque chose de nouveau que Java a: jmc (contrôle de mission Java). Vous pouvez utiliser jconsole pour vous connecter à jvm (local ou distant) et une fois connecté, accédez à l'onglet mémoire et surveillez la croissance du tas, qui devrait disposer d'un cache de métaspace et de code et d'un espace de classe compressé . Et maintenant relier 

jmc

pour vous connecter à la VM, puis une fois que vous êtes connecté, cliquez sur "Commandes de diagnostic" dans la JMC, qui se trouve dans le coin supérieur droit. Puisque nous avons activé UnlockDiagnosticVMOptions, GC.class_stats peut être exécuté. Vous voudrez peut-être l'exécuter avec afficher toutes les colonnes et imprimer au format csv. Donc, la commande ressemblera à: 

GC.class_stats -all=true -csv=true

Vous pouvez ensuite comparer les statistiques de classe sur différentes périodes et déterminer quelles classes sont à l'origine du problème (croissance de métaspace) ou quelles classes ont des informations associées (méthode/données de méthode) dans le métaspace. Comment analyser les sorties csv collectées au fil du temps: eh bien, je prendrais ce csv et le chargerais dans deux tables similaires (représentant csv) dans une base de données ou un autre endroit pour comparer les sorties csv de GC.class_stats où je peux exécuter du SQL ou tout autre outil d'analyse. Cela donnerait une meilleure idée de ce qui se développe exactement dans le méta-espace . Les statistiques de classe GC ont les colonnes suivantes:

Index, Super, InstSize, InstCount, InstBytes, Miroir, KlassBytes, K_secondary_supers, VTAB, CCFI, OopMap, IK_methods, IK_method_ordering, IK_default_methods, IK_default_vtable_indices, IK_local_interfaces, IK_transitive_interfaces, IK_fields, IK_inner_classes, IK_signers, class_annotations, class_type_annotations, fields_annotations, fields_type_annotations, methods_annotations, method_parameter_annotations, method_type_annotations, method_default_annotations, annotations, Cp, CpTags, CpCache, CpOperands, CpRefMap, CpAll, MethodCount, MethodBytes, ConstMethod, MethodP

J'espère que ça aide. En outre, il semble que le bogue soit lié à Java 8 s'il ne provoque aucune fuite dans la version 1.7. 

De plus, les classes ne seront pas déchargées du méta-espace si l'une d'entre elles contient une référence à classloader. Si vous savez que vos chargeurs de classe sont supposés être GCed et que personne ne devrait détenir la référence à votre chargeur de classes, vous pouvez revenir au tas dump dans visualvm et cliquer sur l'instance du chargeur de classes et cliquer avec le bouton droit de la souris pour trouver la "racine GC la plus proche". vous qui détient la référence aux classloaders.

19
dillip pattnaik

nous avions un problème similaire et la cause première était que les fichiers de classe 60K étaient chargés dans la mémoire métaspace, mais rien ne se déchargeait.L'addition en dessous de JVM arg corrigeait le problème.

-Dcom.Sun.xml.bind.v2.bytecode.ClassTailor.noOptimize=true

https://issues.Apache.org/jira/browse/CXF-2939

J'espère que cela t'aides.

4
user1550159

également si certains déploient automatiquement, comme Tomcat, NE gardez PAS les sauvegardes dans Tomcat\webapps, sinon il essaiera de charger la sauvegarde et d'entrer en collision avec ces ressources.

0
Dove