C'est un problème que j'essaie de résoudre depuis quelques mois maintenant. J'ai une application Java qui traite les flux xml et stocke le résultat dans une base de données. Cela a donné lieu à des problèmes de ressources intermittents qui sont très difficiles à localiser.
Le contexte : Sur la boîte de production (où le problème est le plus visible), je n'ai pas un accès particulièrement bon à la boîte, et j'ai été incapable de faire fonctionner Jprofile. Cette boîte est une machine 64bit quad-core, 8gb exécutant centos 5.2, tomcat6, et java 1.6.0.11. Elle démarre avec les options java suivantes
JAVA_OPTS="-server -Xmx5g -Xms4g -Xss256k -XX:MaxPermSize=256m -XX:+PrintGCDetails -
XX:+PrintGCTimeStamps -XX:+UseConcMarkSweepGC -XX:+PrintTenuringDistribution -XX:+UseParNewGC"
La pile technologique est la suivante :
- Centos 64 bits 5.2
- Java 6u11
- Tomcat 6
- Spring/WebMVC 2.5
- Hibernate 3
- Quartz 1.6.1
- DBCP 1.2.1
- Mysql 5.0.45
- Ehcache 1.5.0
- (et bien sûr une foule d'autres dépendances, notamment les bibliothèques jakarta-commons)
Le plus proche que je puisse faire pour reproduire le problème est une machine 32 bits avec des exigences de mémoire plus faibles. J'ai le contrôle là-dessus. Je l'ai sondé à mort avec JProfiler et j'ai corrigé de nombreux problèmes de performance (problèmes de synchronisation, précompilation/mise en cache des requêtes xpath, réduction du threadpool, suppression des pré-recherches hibernate inutiles et réchauffement excessif du cache pendant le traitement).
Dans chaque cas, le profileur a montré qu'ils consommaient d'énormes quantités de ressources pour une raison ou une autre, et qu'ils n'étaient plus les principaux consommateurs de ressources une fois les changements effectués.
Le problème : La JVM semble ignorer complètement les paramètres d'utilisation de la mémoire, remplit toute la mémoire et ne répond plus. C'est un problème pour le client, qui s'attend à un sondage régulier (5 minutes et 1 minute de relance), ainsi que pour nos équipes d'exploitation, qui sont constamment informées qu'une boîte ne répond plus et doivent la redémarrer. Il n'y a rien d'autre d'important qui fonctionne sur cette boîte.
Le problème apparaît pour être la collecte des ordures. Nous utilisons le collecteur ConcurrentMarkSweep (comme indiqué ci-dessus) parce que le collecteur STW d'origine provoquait des timeouts JDBC et devenait de plus en plus lent. Les journaux montrent qu'au fur et à mesure que l'utilisation de la mémoire augmente, le collecteur commence à générer des échecs cms et revient au collecteur original Stop-the-World, qui semble alors ne pas collecter correctement.
Cependant, lorsque j'utilise jprofiler, le bouton "Run GC" semble bien nettoyer la mémoire au lieu de montrer une empreinte croissante, mais comme je ne peux pas connecter jprofiler directement à la machine de production et que la résolution des points chauds ne semble pas fonctionner, il me reste à régler la Garbage Collection à l'aveugle.
Ce que j'ai essayé :
- Profiler et fixer les points chauds.
- Utilisation des collecteurs de déchets STW, Parallel et CMS.
- Exécution avec des tailles de tas min/max à 1/2, 2/4, 4/5, 6/6 incréments.
- Fonctionne avec de l'espace permgen par incréments de 256M jusqu'à 1Gb.
- De nombreuses combinaisons de ce qui précède.
- J'ai également consulté la JVM [tuning reference] (http://java.sun.com/javase/technologies/hotspot/gc/gc\_tuning\_6.html), mais je n'ai pas trouvé d'explication à ce comportement ni d'exemples de paramètres de réglage à utiliser dans une telle situation.
- J'ai également (sans succès) essayé jprofiler en mode hors ligne, en me connectant avec jconsole, visualvm, mais je n'arrive pas à trouver quelque chose qui permette d'intercepter les données de mon journal gc.
Malheureusement, le problème apparaît également de manière sporadique, il semble être imprévisible, il peut fonctionner pendant des jours ou même une semaine sans avoir de problèmes, ou il peut échouer 40 fois en une journée, et la seule chose que je peux attraper de manière constante est que la collecte des déchets est en train de se produire.
Quelqu'un peut-il donner des conseils sur :
a) Pourquoi une JVM utilise-t-elle 8 gigaoctets physiques et 2 gigaoctets d'espace de pagination alors qu'elle est configurée pour en utiliser moins de 6.
b) Une référence à l'accord GC qui explique réellement ou donne des exemples raisonnables de quand et avec quel type de réglage utiliser les collections avancées.
c) Une référence aux fuites de mémoire java les plus courantes (je comprends les références non réclamées, mais je veux dire au niveau de la bibliothèque/du cadre, ou quelque chose de plus inhérent aux structures de données, comme les hashmaps).
Merci pour toutes les informations que vous pourrez nous fournir.
EDIT
Emil H :
1) Oui, mon cluster de développement est un miroir des données de production, jusqu'au serveur média. La principale différence est le 32/64bit et la quantité de RAM disponible, que je ne peux pas reproduire très facilement, mais le code, les requêtes et les paramètres sont identiques.
2) Il y a un certain code hérité qui repose sur JaxB, mais en réorganisant les tâches pour essayer d'éviter les conflits de programmation, j'ai éliminé cette exécution en général puisqu'elle est exécutée une fois par jour. L'analyseur primaire utilise des requêtes XPath qui font appel au paquet java.xml.xpath. C'était la source de quelques problèmes, d'une part les requêtes n'étaient pas pré-compilées, et d'autre part les références à celles-ci étaient dans des chaînes codées en dur. J'ai créé un cache threadsafe (hashmap) et fait en sorte que les références aux requêtes xpath soient des chaînes statiques finales, ce qui a considérablement réduit la consommation de ressources. Les requêtes représentent toujours une grande partie du traitement, mais c'est normal car c'est la principale responsabilité de l'application.
3) Une note supplémentaire, l'autre consommateur principal est les opérations d'image de JAI (retraitement des images à partir d'un flux). Je ne suis pas familier avec les bibliothèques graphiques de Java, mais d'après ce que j'ai trouvé, elles ne sont pas particulièrement perméables.
(merci pour les réponses jusqu'à présent, les gens !)
UPDATE :
J'ai pu me connecter à l'instance de production avec VisualVM, mais il avait désactivé l'option de visualisation / exécution du GC (bien que je puisse le visualiser localement). La chose intéressante : l'allocation du tas de la VM obéit aux JAVA_OPTS, et le tas réellement alloué est confortablement installé à 1-1,5 giga, et ne semble pas fuir, mais la surveillance au niveau de la boîte montre toujours un modèle de fuite, mais il n'est pas reflété dans la surveillance de la VM. Il n'y a rien d'autre qui tourne sur cette boîte, donc je suis perplexe.