Mémoire entièrement utilisée par Java ConcurrentHashMap (sous Tomcat)

Il s’agit d’une stack de mémoire (servant de cache) constituée uniquement d’un ConcurrentHashMap (CHM) statique.

Toutes les données de requête HTTP entrantes sont stockées dans ce ConcurrentHashMap. Et il existe un processus de planification asynchrone qui extrait les données du même ConcurrentHashMap et supprime la clé.value après les avoir stockées dans la firebase database.

Ce système fonctionne correctement mais il suffit de découvrir, selon les critères suivants, que la mémoire était pleinement utilisée (2,5 Go) et que tout le temps processeur était utilisé pour effectuer la GC:

-concurrent http hit de 1000 / s

-maintenir le même hit simultané pendant 15 minutes

Le processus asynchrone enregistre la taille restante du CHM chaque fois qu’il écrit dans la firebase database. Le CHM.size () maintient autour de Min: 300 à Max: 3500

Je pensais qu’il y avait une fuite de mémoire sur cette application. J’ai donc utilisé Eclipse MAT pour regarder le tas. Après avoir exécuté le rapport suspect, j’ai obtenu ces commentaires de MAT:

Une instance de “org.apache.catalina.session.StandardManager” chargée par “org.apache.catalina.loader.StandardClassLoader @ 0x853f0280” occupe 2 135 429 456 octets (94,76%). La mémoire est accumulée dans une instance de “java.util.concurrent.ConcurrentHashMap $ Segment []” chargé par “”.

3,646,166 instances of java.util.concurrent.ConcurrentHashMap$Segment retain >= 2,135,429,456 bytes. 

et

 Length # Objects Shallow Heap Retained Heap 0 3,646,166 482,015,968 >= 2,135,429,456 

La longueur 0 ci-dessus, je la traduis comme un enregistrement de longueur vide dans le CHM (chaque fois que j’appelle la méthode CHM.remove ()). Il est cohérent avec le nombre d’enregistrements à l’intérieur de la firebase database, 3 646 166 enregistrements se trouvaient dans la firebase database lors de la création de cette sauvegarde.

Le scénario étrange est le suivant : si j’interromps le test de résistance, l’utilisation de la mémoire du tas sera progressivement réduite à 25 Mo. Cela prend environ 30 à 45 minutes. J’ai re-simulé cette application et les courbes ressemblent au graphique VisualVM ci-dessous: texte alt

Heres les questions:

1) Cela ressemble-t-il à une fuite de mémoire?

2) Chaque appel de remove(Object key, Object value) pour supprimer une de CHM, cet object supprimé obtient-il un GC?

3) Est-ce quelque chose à voir avec les parameters du GC? J’ai ajouté les parameters GC suivants mais sans aide:

 -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:GCTimeRatio=19 -XX:+PrintGCTimeStamps -XX:ParallelGCThreads=6 -verbose:gc 

4) Toute idée pour résoudre ceci est très appréciée! 🙂

NOUVEAU 5) Est-ce possible parce que toutes mes références sont une référence difficile? Je pense que tant que la session HTTP est terminée, toutes les variables qui ne sont pas statiques sont maintenant disponibles pour GC.

NOUVEAU Note J’ai essayé de remplacer le CHM par ehcache 2.2.0, mais j’ai le même problème OutOfMemoryException. Je suppose qu’ehcache utilise également ConcurrentHashMap.

Spécification du serveur:

-Xeon Quad core, 8 threads.

-4 Go de mémoire

-Windows 2008 R2

-Tomcat 6.0.29

Ce problème a moi bug pour un mauvais 7 jours! Et finalement j’ai découvert le vrai problème! Vous trouverez ci-dessous les tâches que j’ai essayées mais que je n’ai pas réussi à résoudre l’exception OutOfMemory:

-changer de simulthashmap à ehcache. (il s’avère qu’ehcache utilise également ConcurrentHashMap)

-changer toute la référence difficile à la référence souple

-Override the AbstractMap à côté de concurrnetHashMap selon les suggestions du Dr. Heinz M. Kabutz

La question à un million de dollars est vraiment “pourquoi 30 à 45 minutes plus tard, la mémoire commence à revenir dans le pool de tas?”

La cause réelle était que quelque chose contenait encore la session de la variable, et que la cause de la session HTTP dans Tomcat est toujours active! Par conséquent, même si la session http a été terminée, mais si le délai d’expiration est de 30 minutes, tomcat conservera les informations de session pendant 30 minutes avant que la JVM puisse les définir. Le problème est résolu immédiatement après la modification du délai d’attente à 1 minute en tant que test.

 $tomcat_folder\conf\web.xml  1  

J’espère que cela aidera tout le monde avec un problème similaire.

Je pense que vous utilisez trop de données de session qui ne rentreront pas immédiatement en mémoire . Essaye celui-là:

  1. Editez bin/setenv.sh ou partout où les bin/setenv.sh JVM sont définis sur votre lanceur Tomcat:

    Ajoutez -Dorg.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true

    par exemple

     # Default Java options if [ -z "$JAVA_OPTS" ]; then JAVA_OPTS="-server -Djava.awt.headless=true -XX:MaxPermSize=384m -Xmx1024m -Dorg.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true" fi 
  2. Modifiez conf/context.xml , avant ajoutez ceci:

        

Redémarrez Tomcat et votre problème devrait être réglé, car il stockera vos sessions en utilisant le système de fichiers .

Dans ma vue, le paramètre session-timeout = 1 est une solution de contournement qui masque la source du problème et qui est inutilisable dans la plupart des applications pour lesquelles vous avez besoin d’un session-timeout suffisant. Nos applications ( Bippo ) ont généralement une durée de session-timeout de 2880 minutes, soit 2 jours.

Référence: Configuration de Tomcat 7.0 Session Manager

1) Cela ressemble-t-il à une fuite de mémoire?

Oui, si l’application continue à mettre des objects dans la carte et ne les retire jamais, cela pourrait très bien être une fuite de mémoire.

2) Chaque suppression d’appel est supprimée (clé de l’object, valeur de l’object) pour supprimer un CHM, cet object retiré obtient-il un GC?

Les objects ne peuvent être ramassés que s’il n’y a pas de thread en cours qui les référence. La carte n’est qu’un endroit où il est fait référence à l’object. Il pourrait encore y avoir d’autres endroits qui ont des références au même object. Mais garder l’object sur la carte l’empêchera d’être ramassé.

3) Est-ce quelque chose à voir avec les parameters du GC?

Non; si un object est référencé, il ne peut pas être récupéré; Peu importe comment vous modifiez le ramasse-miettes.

Bien sûr, il est trop tard pour répondre, mais uniquement aux autres personnes qui trouveront cette question par recherche. Cela pourrait être utile.

Ces 2 liens sont très utiles
https://issues.apache.org/bugzilla/show_bug.cgi?id=50685
http://wiki.apache.org/tomcat/OutOfMemory

En bref, dans la plupart des cas, il s’agit d’un test ou d’un logiciel de test incorrect. Lorsque certains logiciels personnalisés ouvrent une URL, si ce logiciel ne parvient pas à gérer la session http, tomcat crée une nouvelle session pour chaque requête. Par exemple, il est possible de le vérifier avec un code simple, qui peut être ajouté à JSP.

 System.out.println("session id: " + session.getId()); System.out.println("session obj: " + session); System.out.println("session getCreationTime: " + (new Date(session.getCreationTime())).toSsortingng()); System.out.println("session.getValueNames().length: " + session.getValueNames().length); 

Si l’ID de session est le même pour un utilisateur du sharepoint vue du test de charge, si chaque demande génère un nouvel ID de session, cela signifie que le logiciel de test ne gère pas très bien les sessions et que le résultat ne représente pas la charge des utilisateurs réels .

Pour certaines applications, session.getValueNames (). Length est également important, car par exemple, lorsque l’utilisateur fonctionne normalement, il rest le même, mais lorsque le logiciel de test de charge fait de même, il augmente. Cela signifie également que les logiciels de test de charge ne représentent pas très bien la charge de travail réelle. Dans mon cas, session.getValueNames (). La longueur pour un utilisateur normal était d’environ 100, mais q avec le logiciel de test de chargement après 10 minutes était d’environ 500 et finalement le système plante avec la même erreur OutOfMemory et MAT affiche la même chose:

org.apache.catalina.loader.StandardClassLoader @ 0x853f0280 “occupe 2 135 429 456 (94,76%) octets.

Si vous obtenez cette exception et que vous utilisez la version Spring Boot 1.4.4 RELEASE ou inférieure, définissez la valeur de la propriété “server.session-timeout” en minutes, plutôt que ce qu’elle suggère (en secondes), de sorte que les sessions sur le tas soient nettoyé à temps. Ou vous pouvez utiliser un bean de EmbeddedServletContainerCustomizer mais la valeur fournie sera définie en minutes.

exemple (délai de session de 10 minutes): server.session-timeout = 10 (défini dans le fichier de propriétés) container.setSessionTimeout (10, TimeUnit.SECONDS); (défini dans EmbeddedServletContainerCustomizer)