Was verursacht java.lang.OutOfMemoryError: Es kann kein neuer nativer Thread erstellt werden

1. Einleitung

In diesem Tutorial werden die Ursache und mögliche Abhilfemaßnahmen für java.lang.OutOfMemoryError erläutert: Es kann kein neuer nativer Threadfehler erstellt werden.

2. Das Problem verstehen

2.1. Ursache des Problems

Die meisten Java-Anwendungen sind Multithread-Anwendungen , die aus mehreren Komponenten bestehen, bestimmte Aufgaben ausführen und in verschiedenen Threads ausgeführt werden. Das zugrunde liegende Betriebssystem legt jedoch eine Obergrenze für die maximale Anzahl von Threads fest , die eine Java-Anwendung erstellen kann.

Die JVM löst einen Fehler aus, bei dem kein neuer nativer Thread erstellt werden kann, wenn die JVM das zugrunde liegende Betriebssystem nach einem neuen Thread fragt und das Betriebssystem keine neuen Kernel-Threads erstellen kann, die auch als Betriebssystem- oder System-Threads bezeichnet werden . Die Reihenfolge der Ereignisse ist wie folgt:

  1. Eine Anwendung, die in der Java Virtual Machine (JVM) ausgeführt wird, fordert einen neuen Thread an
  2. Der native JVM-Code sendet eine Anforderung an das Betriebssystem, einen neuen Kernel-Thread zu erstellen
  3. Das Betriebssystem versucht, einen neuen Kernel-Thread zu erstellen, für den eine Speicherzuweisung erforderlich ist
  4. Das Betriebssystem lehnt die native Speicherzuweisung aus beiden Gründen ab
    • Der anfordernde Java-Prozess hat seinen Speicheradressraum erschöpft
    • Das Betriebssystem hat seinen virtuellen Speicher aufgebraucht
  5. Der Java-Prozess gibt dann den java.lang.OutOfMemoryError zurück: Es kann kein neuer nativer Thread- Fehler erstellt werden

2.2. Thread-Zuordnungsmodell

Ein Betriebssystem verfügt normalerweise über zwei Arten von Threads: Benutzer-Threads (von einer Java-Anwendung erstellte Threads) und Kernel-Threads . Benutzer-Threads werden über den Kernel-Threads unterstützt und die Kernel-Threads werden vom Betriebssystem verwaltet.

Zwischen ihnen gibt es drei gemeinsame Beziehungen:

  1. Many-To-One - Viele Benutzer-Threads werden einem einzelnen Kernel-Thread zugeordnet
  2. Eins-zu-Eins - Eine Benutzer-Thread-Zuordnung zu einem Kernel-Thread
  3. Viele-zu-Viele - Viele Benutzer-Threads werden auf eine kleinere oder gleiche Anzahl von Kernel-Threads gemultiplext

3. Reproduzieren des Fehlers

Wir können dieses Problem leicht neu erstellen, indem wir Threads in einer Endlosschleife erstellen und die Threads dann warten lassen:

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

Da wir eine Stunde lang an jedem Thread festhalten und kontinuierlich neue erstellen, erreichen wir schnell die maximale Anzahl von Threads vom Betriebssystem aus.

4. Lösungen

Eine Möglichkeit, diesen Fehler zu beheben, besteht darin, die Thread-Limit-Konfiguration auf Betriebssystemebene zu erhöhen.

Dies ist jedoch keine ideale Lösung, da der OutOfMemoryError wahrscheinlich einen Programmierfehler anzeigt. Schauen wir uns einige andere Möglichkeiten an, um dieses Problem zu lösen.

4.1. Executor Service Framework nutzen

Durch die Nutzung des Executor-Service-Frameworks von Java für die Thread-Verwaltung kann dieses Problem bis zu einem gewissen Grad behoben werden. Das Standard-Executor-Service-Framework oder eine benutzerdefinierte Executor-Konfiguration kann die Thread-Erstellung steuern.

Mit der Executors # newFixedThreadPool- Methode können Sie die maximale Anzahl von Threads festlegen , die gleichzeitig verwendet werden können:

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)));

Im obigen Beispiel erstellen wir zuerst einen Pool mit festen Threads mit fünf Threads und einer ausführbaren Aufgabe, bei der die Threads eine Stunde warten. Wir senden dann zehn solcher Aufgaben an den Thread-Pool und bestätigen, dass fünf Aufgaben in der Executor-Service-Warteschlange warten.

Da der Thread-Pool über fünf Threads verfügt, können maximal fünf Aufgaben gleichzeitig ausgeführt werden.

4.2. Erfassen und Analysieren des Thread-Dumps

Das Erfassen und Analysieren des Thread-Dumps ist hilfreich, um den Status eines Threads zu verstehen.

Schauen wir uns einen Beispiel-Thread-Dump an und sehen, was wir lernen können:

Der obige Thread-Snapshot stammt aus Java VisualVM für das zuvor dargestellte Beispiel. Dieser Schnappschuss zeigt deutlich die kontinuierliche Thread-Erstellung.

Sobald wir feststellen, dass eine kontinuierliche Thread-Erstellung erfolgt, können wir den Thread-Dump der Anwendung erfassen, um den Quellcode zu identifizieren, der die Threads erstellt:

Im obigen Snapshot können wir den Code identifizieren, der für die Thread-Erstellung verantwortlich ist. Dies bietet nützliche Einblicke, um geeignete Maßnahmen zu ergreifen.

5. Schlussfolgerung

In diesem Artikel haben wir etwas über den java.lang.OutOfMemoryError erfahren : Es konnte kein neuer nativer Thread- Fehler erstellt werden, und wir haben festgestellt, dass er durch übermäßige Thread-Erstellung in einer Java-Anwendung verursacht wird.

Wir haben einige Lösungen untersucht, um den Fehler zu beheben und zu analysieren, indem wir das ExecutorService- Framework und die Thread-Dump-Analyse als zwei nützliche Maßnahmen zur Behebung dieses Problems angesehen haben.

Wie immer ist der Quellcode für den Artikel auf GitHub verfügbar.