OutOfMemoryError: unable to create new native thread

L’OutOfMemory est une des erreurs les plus célèbres de la JVM. Comme son nom l’indique, elle se produit quand la JVM n’a plus de mémoire. J’ai dernièrement rencontré cette erreur  et cela m’a permis de découvrir les méandres peu connues de la mémoire de la JVM.

Tout d’abord petit rappel sur la répartition de la mémoire de la JVM:

  • La Heap: Cette zone mémoire permet d’allouer les objets courants, c’est la zone mémoire la plus utilisée. Les options Xmx et Xms permettent de faire varier sa taille. Généralement les OutOfMemory viennent d’un problème dans cette zone.
  •  La PermGen: Cette zone est réservée au chargement des classes. L’option  XX:MaxPermSize permet de définir sa taille maximum. Elle est régulièrement ajustée sur les conteneurs de Servlet contenant beaucoup de JSP.

Les outils fournis en standard sur la JVM tels que jstat, jmap ou plus évolués comme la jconsole et VisualVM permettent de collecter toutes les informations nécessaires  sur ces zones de mémoire. Ils permettent aussi de faire des Heap Dumps pouvant être analysé avec jhat, VisualVM/netBean ou excellent HeapAnalyser.
Pour plus de précision sur ces outils vous pouvez relire le post de François et regarder ce tutorial

Une autre zone de mémoire existe dans la JVM, elle n’est pas bien décrite dans les différentes documentations, elle permet de stocker les piles mémoires des threads créées par la JVM. En effet, pour chaque thread créé à l’intérieur de la JVM, le système alloue un bloc mémoire pour stocker la pile d’exécution qui contient la liste des méthodes appelées ainsi que leurs contextes d’invocations et les informations propres au thread.
La taille de cette pile est par défaut de 512k et peut être modifiée avec l’option Xss ou ThreadStackSize. En cas de dépassement de cette taille, une StackOverflowError est lancée. Il est possible de voir une partie de ces informations dans les “StackTrace” des exceptions.
Les piles des différents threads sont stockées dans le même processus que la JVM mais ne sont pas prises en compte dans la taille de la Heap et de la PermGen; elles viennent donc s’additionner à la taille du processus système de la JVM.
Or cette taille mémoire n’est pas extensible à l’infini. En fonction de l’OS et de l’architecture de la machine, il existe une taille maximum qui ne peut pas être dépassée (sur une Redhat 32bit cette taille est d’environ 3Go ). Une fois cette limite atteinte, toute création de thread entraine un OutOfMemoryError: unable to create new native thread

Xmx + MaxPermSize + (Xss * nombre de thread) = Max Mémoire pour un processus sur l’OS

Plusieurs causes peuvent entrainer une surconsommation de thread:

  • Le thread Leak: ce problème est peu courant mais peut arriver. C’est le même principe que pour le memory leak mais pour les threads. Une portion de code crée un thread  qui ne s’arrête jamais. A chaque exécution du code, un nouveau thread est créé jusqu’au OutOfMemory: unable to create new native thread
  •  Une mauvaise configuration des connecteurs et des pools de threads: La somme des maximums des capacités des connecteurs et des pools de threads ne doit jamais dépasser le nombre de thread maximum de la JVM. Il est même plutôt conseillé de garder une bonne marge de sécurité pour prévenir des créations de thread faites par certains frameworks. Pour donner un ordre d’idée, Mark Thomas lors des rencontres Spring, a conseillé d’avoir en prod des connecteurs Tomcat allant de 200 à 800 threads.

Pour tous ces problèmes, il est possible de faire des threads dumps de l’application à intervalle régulier, de les comparer afin de trouver les nouveaux threads et d’identifier leur provenance (généralement le nom et le code exécuté sont de bons indices).
Pour faire des dumps , on peut utiliser jstack et  visualvm et pour les étudier visualvm ou thread tda ( voir le post d’Emmanuel ). La jconsole et visualvm permettent  aussi de surveiller le nombre total de thread  de la JVM en temps réel, ce qui peut être d’une grande aide.
Si après tous ces tests, vous vous rendez compte que l’application a réellement besoin d’un nombre très important de thread pour subvenir à la demande,  plusieurs solutions sont à envisager:

  • 1 ère solution: la moins coûteuse, modifiez (si cela est possible) le Xmx et/ou le Xss de votre application de manière à pouvoir créer encore plus de thread. Il est ensuite conseillé de bien re-tester votre application.
  • 2 ème solution: modifiez votre architecture physique et/ou logique:
    • si vous êtes sur des machines 32bit, passez en 64 bit de manière à repousser la limite de taille des processus.
    • Modifiez votre application de manière à pouvoir ajouter d’autres instances de la JVM. Rendez l’application plus scaleable horizontalement.
    • Transformez, si vous le pouvez, vos traitements synchrones, en traitements  asynchrones de manière à mieux contrôler vos ressources et limiter les temps d’attente de vos threads.

Pour conclure, le nombre de thread dans une JVM n’est pas extensible à l’infini. Il est utile de garder cette règle à l’esprit quand on déclare des connecteurs HTTP, des pools de thread ou lors de la création d’un thread, cela pourra peut-être vous éviter de voir un beau matin dans vos traces des OutOfMemory: unable to create new native thread ;-)

Plus d’informations sur le wiki de JBoss

TwitterFacebookGoogle+LinkedIn
  • http://m-button.blogspot.com Maxence

    Très bon article Nico !Juste une petite précision, pour RHEL 32Bits, c'est plutôt 2G :http://docs.sun.com/source/819-0084/pt_tuningjava.html#wp57033 Et passer en mode 64 bits est un choix à bien peser … Cela engendre des inconvénients, au niveau perf notamment.  

    • pariviere

      Justement c'est quoi ces inconvénients? De mon point de vue t'augmente la consommation mémoire mais qui est justement pallié par le fait de pouvoir en adresser plus. Y'en a d'autres?

      • http://m-button.blogspot.com Maxence

        Salut PA !

        Eh bien, tu as le full GC, qui lorsqu'il passe sur une JVM de 8G va forcément consommer plus de temps que sur une JVM de 2G. Temps de pause accru = throughput en berne. (évidemment tu peux tuner tes algos pour en avoir un minimum et privilégier des GC partiels mais bon …)

        Et l'autre point concerne la taille des pointeurs (x2 en 64 bits), ce qui du coup induit une occupation doublée de la mémoire. Je te laisse lire cette doc de Sun :

        http://java.sun.com/docs/hotspot/HotSpotFAQ.html#

        Donc je maintiens : à moins d'avoir vraiment besoin d'une taille de JVM conséquente, autant rester en 32bits et tenter d'optimiser l'existant.

        • pariviere

          Hello Max!Du coup, si on a besoin d'adressé d'un heap space au dessus de 1,4~2Go. On aligne les JVM 32bits les unes à côté des autres (en espérant pouvoir faire du clustering au dessus) ou on fait fit des problèmes et on monte en 64 bits?Je t'avouerai que pour ma part, mis à part incompatibilité connue, je prèfèrere la montée en 64 bits.  Plus vite on se débarassera de l'existant, mieux ce sera. Sans compter la course incessante des performances CPU et l'effondrement du prix de la RAM.

          • http://m-button.blogspot.com Maxence

            Si tu as besoin d'une JVM qui stocke beaucoup de données en mémoire, tu peux évidemment passer sur une JVM 64 bits : je n'ai pas dit que c'était une mauvaise solution :)Si les données stockées en mémoire servent à un cache, il existe effectivement des alternatives de cache distribué (Oracle Coherence, Terracotta …) qui peuvent offrir une solution intéressante.Mais le scaling horizontal ne répond pas à la même problématique : tu améliores la tolérance à la panne et la tenue en charge. Si ta JVM a besoin de 4G de RAM, ajouter une autre JVM ne changera rien au problème. Je comprends bien ton envie de démocratisation des JVM 64 bits :)Mais là où j'émets des réserves, c'est quand la rapidité de la JVM prime sur le volume des données stockées en mémoire.Par ex, pour un calcul financier, les JVM 32 bits me semblent simplement plus adaptées (avec des JVM préparées comme Oracle JRockit Real Time par exemple)http://www.oracle.com/technology/products/jrockit/jrrt/index.html(Désolé pour les citations de produits BEA | Oracle ;) )

    • npeters

      <meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8"><title></title><meta name="GENERATOR" content="OpenOffice.org 3.0 (Win32)"><style type="text/css">

      <!–{12277350328410}–>

      </style> </meta></meta>J'ai refait des recherches au sujet de la taille max d'un processus et je suis tombé sur cet article IBM: sur la JVM 32 et la JVM 64http://www-01.ibm.com/support/docview.wss?uid=swg… Page 7: on retrouve un schéma qui explique les différentes zones mémoire. on y retrouve la Heap, la Thread Stacks,…Page 10: un autre schéma décrit la taille utilisable par un processus qui est bien de 3Go. La conclusion de l'article n'est en effet pas très positive sur la JVM 64… mais on en est encore qu'au début de cette technologie, des progrès sont à venir (compression de pointeur)  et peut-être devra t'on changer l'architecture de nos applications pour mieux exploiter ces points forts !  Un dernier lien sur le site d'oracle qui illustre bien le lien entre la taille de la heap et le nombre de thread  http://blogs.oracle.com/gverma/2008/03/redhat_lin…   <p style="margin-bottom: 0cm;">