Ce qui cause java.lang.OutOfMemoryError: impossible de créer un nouveau thread natif

1. Introduction

Dans ce didacticiel, nous discuterons de la cause et des solutions possibles de l' erreur java.lang.OutOfMemoryError: impossible de créer une nouvelle erreur de thread natif .

2. Comprendre le problème

2.1. Cause du problème

La plupart des applications Java sont de nature multithread , constituées de plusieurs composants, exécutant des tâches spécifiques et exécutées dans différents threads. Cependant, le système d'exploitation (OS) sous-jacent impose une limite au nombre maximum de threads qu'une application Java peut créer.

La machine virtuelle Java génère une erreur de création de thread natif impossible lorsque la machine virtuelle Java demande un nouveau thread au système d'exploitation sous-jacent et que le système d'exploitation est incapable de créer de nouveaux threads de noyau également appelés threads de système d'exploitation ou système . La séquence des événements est la suivante:

  1. Une application s'exécutant dans la machine virtuelle Java (JVM) demande un nouveau thread
  2. Le code natif JVM envoie une requête au système d'exploitation pour créer un nouveau thread de noyau
  3. Le système d'exploitation tente de créer un nouveau thread du noyau qui nécessite une allocation de mémoire
  4. Le système d'exploitation refuse l'allocation de mémoire native car soit
    • Le processus Java demandeur a épuisé son espace d'adressage mémoire
    • Le système d'exploitation a épuisé sa mémoire virtuelle
  5. Le processus Java renvoie ensuite le java.lang.OutOfMemoryError: impossible de créer une nouvelle erreur de thread natif

2.2. Modèle d'allocation des threads

Un système d'exploitation a généralement deux types de threads: les threads utilisateur (threads créés par une application Java) et les threads du noyau . Les threads utilisateur sont pris en charge au-dessus des threads du noyau et les threads du noyau sont gérés par le système d'exploitation.

Entre eux, il existe trois relations communes:

  1. Many-To-One - De nombreux threads utilisateur correspondent à un seul thread du noyau
  2. One-To-One - Un thread utilisateur mappe à un thread du noyau
  3. Many-To-Many - De nombreux threads utilisateur se multiplexent vers un nombre inférieur ou égal de threads du noyau

3. Reproduction de l'erreur

Nous pouvons facilement recréer ce problème en créant des threads dans une boucle continue, puis en faisant attendre les threads:

while (true) { new Thread(() -> { try { TimeUnit.HOURS.sleep(1);     } catch (InterruptedException e) { e.printStackTrace(); } }).start(); }

Puisque nous maintenons chaque thread pendant une heure, tout en en créant continuellement de nouveaux, nous atteindrons rapidement le nombre maximum de threads du système d'exploitation.

4. Solutions

Une façon de résoudre cette erreur consiste à augmenter la configuration de la limite de thread au niveau du système d'exploitation.

Cependant, ce n'est pas une solution idéale car l' OutOfMemoryError indique probablement une erreur de programmation. Examinons d'autres moyens de résoudre ce problème.

4.1. Tirer parti du cadre de service des exécuteurs

Tirer parti du cadre de service d'exécuteur de Java pour l'administration des threads peut résoudre ce problème dans une certaine mesure. L'infrastructure de service d'exécuteur par défaut, ou une configuration d'exécuteur personnalisée, peut contrôler la création de thread.

Nous pouvons utiliser la méthode Executors # newFixedThreadPool pour définir le nombre maximum de threads pouvant être utilisés à la fois:

ExecutorService executorService = Executors.newFixedThreadPool(5); Runnable runnableTask = () -> { try { TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { // Handle Exception } }; IntStream.rangeClosed(1, 10) .forEach(i -> executorService.submit(runnableTask)); assertThat(((ThreadPoolExecutor) executorService).getQueue().size(), is(equalTo(5)));

Dans l'exemple ci-dessus, nous créons d'abord un pool de threads fixes avec cinq threads et une tâche exécutable qui fait attendre les threads pendant une heure. Nous soumettons ensuite dix de ces tâches au pool de threads et affirmons que cinq tâches sont en attente dans la file d'attente du service de l'exécuteur.

Étant donné que le pool de threads a cinq threads, il peut gérer un maximum de cinq tâches à tout moment.

4.2. Capture et analyse du vidage des threads

La capture et l'analyse du vidage de threads sont utiles pour comprendre l'état d'un thread.

Regardons un exemple de vidage de thread et voyons ce que nous pouvons apprendre:

L'instantané de thread ci-dessus provient de Java VisualVM pour l'exemple présenté précédemment. Cet instantané montre clairement la création de threads en continu.

Une fois que nous identifions qu'il existe une création de threads continue, nous pouvons capturer le vidage de thread de l'application pour identifier le code source créant les threads:

Dans l'instantané ci-dessus, nous pouvons identifier le code responsable de la création du thread. Cela fournit des informations utiles pour prendre les mesures appropriées.

5. Conclusion

Dans cet article, nous avons découvert l' erreur java.lang.OutOfMemoryError: impossible de créer une nouvelle erreur de thread natif , et nous avons vu qu'elle était causée par une création excessive de thread dans une application Java.

Nous avons exploré quelques solutions pour résoudre et analyser l'erreur en examinant le cadre ExecutorService et l'analyse de vidage des threads comme deux mesures utiles pour résoudre ce problème.

Comme toujours, le code source de l'article est disponible à l'adresse over sur GitHub.