J'ai une application Web Java s'exécutant sur Tomcat 7 qui semble présenter une fuite de mémoire. L'utilisation moyenne de la mémoire de l'application augmente de manière linéaire avec le temps sous charge (déterminée à l'aide de JConsole). Lorsque l'utilisation de la mémoire atteint le plateau, les performances se dégradent considérablement. Les temps de réponse vont de ~ 100 ms à [300 ms, 2500 ms], ce qui pose donc de réels problèmes.
Profil de mémoire JConsole de mon application:
En utilisant VisualVM, je constate qu'au moins la moitié de la mémoire est utilisée par des tableaux de caractères (c'est-à-dire char []) et que la plupart (environ le même nombre de 300 000 occurrences) des chaînes sont l'une des suivantes: "Échec de l'allocation" , "Copier", "Fin de la GC mineure", qui semblent tous être liés à la notification de récupération de place. Autant que je sache, l'application ne surveille pas le ramasse-miettes. VisualVM ne peut trouver aucune racine GC pour ces chaînes. J'ai donc du mal à le localiser.
Dump de mémoire de l'analyseur de mémoire:
Je ne peux pas expliquer pourquoi les plateaux d’utilisation de la mémoire sont pareils, mais j’ai une théorie selon laquelle les performances se dégradent une fois que c’est le cas. Si la mémoire est fragmentée, l'application peut prendre beaucoup de temps pour allouer un bloc de mémoire contigu pour traiter les nouvelles demandes.
En comparant cela avec l'application d'état du serveur Tomcat intégrée, la mémoire augmente et se stabilise à, mais elle n'atteint pas un niveau plancher comme celui de mon application. Il n’a pas non plus le nombre élevé de caractères inaccessibles [].
Profil de mémoire JConsole de l'application d'état du serveur Tomcat:
Sauvegarde de mémoire Memory Analyzer de l'application de statut du serveur Tomcat:
Où ces chaînes peuvent-elles être attribuées et pourquoi ne sont-elles pas collectées? Existe-t-il des paramètres Tomcat ou Java susceptibles d’affecter ce problème? Existe-t-il des packages spécifiques qui pourraient affecter cela?
J'ai supprimé la configuration JMX suivante de Tomcat\bin\setenv.bat
:
set "Java_OPTS=%Java_OPTS%
-Dcom.Sun.management.jmxremote=true
-Dcom.Sun.management.jmxremote.port=9090
-Dcom.Sun.management.jmxremote.ssl=false
-Dcom.Sun.management.jmxremote.authenticate=false"
Je ne peux plus obtenir de dumps de mémoire détaillés, mais le profil de la mémoire est beaucoup mieux:
24 heures plus tard, le profil de mémoire a le même aspect:
Je suggérerais d'utiliser memoryAnalyzer pour analyser votre tas, cela donne beaucoup plus d'informations.
http://www.Eclipse.org/mat/ Il existe une application autonome et une application intégrée Eclipse. Il suffit d’exécuter jmap sur votre application et d’analyser le résultat avec.
Je peux recommander jvisualvm qui accompagne chaque installation Java. Démarrez le programme, connectez-vous à votre application Web. Accédez à Monitor -> Heap Dump. Cela peut maintenant prendre un peu de temps (en fonction de la taille)… .. La navigation dans le tas est très simple, mais vous devez vous comprendre (ce n'est pas trop compliqué), par exemple.
Accédez à Classes (dans le tas), sélectionnez Java.lang.String, cliquez avec le bouton droit de la souris sur Afficher dans la vue Instances. Après cela, vous verrez sur la table de gauche} _ String les instances actuellement actives sur votre système . Cliquez sur une instance String et vous verrez apparaître String. préfère la partie droite-supérieure de la table de droite, comme la valeur de la chaîne String.
Dans la partie en bas à droite de la table de droite, vous verrez où l'occurrence de ceci _ String est référencé depuis. Ici, vous devez vérifier où le plus grand nombre de vos * String * s sont référencés. Mais avec votre cas (176/210, bonne possibilité de trouver quelques exemples de String qui causent rapidement vos problèmes), il devrait être clair, après une inspection, où réside le problème.
Je viens de rencontrer le même problème dans une application totalement différente, de sorte que Tomcat7 n'est probablement pas à blâmer. Memory Analyzer affiche 10 millions d'instances de chaîne inaccessibles dans le processus (en cours d'exécution depuis environ 2 mois) et la plupart/toutes ont des valeurs liées à la récupération de place (par exemple, "Allocation Failure", "fin de la GC mineure")
Analyseur de mémoire
Full GC fonctionne maintenant tous les 2 secondes, mais ces chaînes ne sont pas collectées. Je suppose que nous avons rencontré un bogue dans le code du GC. Nous utilisons la version Java suivante:
$ Java -version
Java version "1.7.0_06"
Java(TM) SE Runtime Environment (build 1.7.0_06-b24)
Java HotSpot(TM) 64-Bit Server VM (build 23.2-b09, mixed mode)
et les paramètres VM suivants:
-Xms256m -Xmx768m -server -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC
-XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:NewSize=32m -XX:MaxNewSize=64m
-XX:SurvivorRatio=8 -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails
-Xloggc:/path/to/file
Le plateau est dû au fait que la mémoire disponible tombe en dessous du seuil de pourcentage par défaut, ce qui provoque un Full GC. Cela explique pourquoi les performances chutent car la machine virtuelle Java est constamment en pause pendant qu'elle essaie de trouver et de libérer de la mémoire.
Je conseillerais généralement de regarder les caches d'objets, mais dans votre cas, je pense que votre taille de segment de mémoire est tout simplement trop basse pour une instance Tomcat + webapp. Je recommanderais d'augmenter votre segment de mémoire à 1G (-Xms1024m -Xmx1024m
), puis de revoir votre utilisation de la mémoire.
Si vous observez toujours le même type de comportement, vous devez effectuer un autre vidage de segment de mémoire et consulter les plus gros consommateurs, après String et Char. D'après mon expérience, il s'agit généralement de mécanismes de mise en cache. Augmentez davantage votre mémoire ou réduisez si possible les magasins de mise en cache. Certains caches ne définissent que le nombre d'objets. Vous devez donc comprendre la taille de chaque objet mis en cache.
Une fois que vous avez compris votre utilisation de la mémoire, vous pourrez peut-être la réduire à nouveau, mais IMHO 512 Mo serait un minimum.
Mettre à jour:
Ne vous inquiétez pas des objets inaccessibles, car ils doivent être nettoyés par le GC. En outre, il est normal que les principaux consommateurs par type soient String et Char. La plupart des objets contiennent une sorte de chaîne. Il est donc logique que les chaînes et les caractères soient les plus courants par fréquence. Comprendre ce qui contient les objets contenant les chaînes est la clé pour trouver des consommateurs de mémoire.
Par accident, je suis tombé sur les lignes suivantes du fichier conf/catalina.properties de notre Tomcat qui active la mise en cache des chaînes. Cela pourrait être lié à votre cas si vous en avez activé un. Il semble d’autres avertissent d’utiliser cette fonctionnalité.
Tomcat.util.buf.StringCache.byte.enabled=true
#Tomcat.util.buf.StringCache.char.enabled=true
#Tomcat.util.buf.StringCache.trainThreshold=500000
#Tomcat.util.buf.StringCache.cacheSize=5000
Essayez d'utiliser MAT et assurez-vous que, lorsque vous analysez le tas de données, évitez de laisser tomber les objets inaccessibles.
Pour ce faire, suivez le tutoriel ici .
Ensuite, vous pouvez exécuter une simple analyse de fuite de mémoire ( Ceci est un bon tutoriel)
Cela devrait vous conduire rapidement à la cause première.
Comme cela semble peu spécifique, un candidat aurait été JSF. Mais j'aurais alors prévu que les cartes de hachage fuiraient également.
Si vous utilisez JSF: Dans web.xml , vous pouvez essayer:
En ce qui concerne les outils: JavaMelody pourrait être intéressant pour les statistiques en continu, mais demande des efforts.