1. 序章

このチュートリアルでは、 java .lang.OutOfMemoryError:新しいネイティブスレッドエラーを作成できない原因と考えられる解決策について説明します。

2. 問題を理解する

2.1. 問題の原因

ほとんどのJavaアプリケーションは本質的にマルチスレッドであり、複数のコンポーネントで構成され、特定のタスクを実行し、異なるスレッドで実行されます。 ただし、基盤となるオペレーティングシステム(OS)は、Javaアプリケーションが作成できるスレッドの最大数に上限を課しています。

JVMが基盤となるOSに新しいスレッドを要求し、OSがOSまたはシステムスレッドとも呼ばれる新しいカーネルスレッドを作成できない場合、JVMは新しいネイティブスレッドを作成できませんエラーをスローします[ X215X]。  イベントのシーケンスは次のとおりです。

  1. Java仮想マシン(JVM)内で実行されているアプリケーションが、新しいスレッドを要求します
  2. JVMネイティブコードは、新しいカーネルスレッドを作成するための要求をOSに送信します
  3. OSは、メモリ割り当てを必要とする新しいカーネルスレッドを作成しようとします
  4. OSは、次のいずれかの理由でネイティブメモリの割り当てを拒否します
    • 要求しているJavaプロセスがメモリアドレス空間を使い果たしました
    • OSが仮想メモリを使い果たしました
  5. 次に、Javaプロセスは java.lang.OutOfMemoryErrorを返します:新しいネイティブスレッドを作成できませんエラー

2.2. スレッド割り当てモデル

OSには通常、 2種類のスレッドがあります。ユーザースレッド(Javaアプリケーションによって作成されたスレッド)とカーネルスレッドです。 ユーザースレッドはカーネルスレッドの上でサポートされ、カーネルスレッドはOSによって管理されます。

それらの間には、3つの一般的な関係があります。

  1. 多対1–多くのユーザースレッドが単一のカーネルスレッドにマップされます
  2. 1対1–1つのユーザースレッドを1つのカーネルスレッドにマップ
  3. 多対多–多くのユーザースレッドが少数または同数のカーネルスレッドに多重化されます

3. エラーの再現

連続ループでスレッドを作成し、スレッドを待機させることで、この問題を簡単に再現できます。

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

各スレッドを1時間保持しているため、新しいスレッドを継続的に作成しながら、OSからのスレッドの最大数にすばやく到達します。

4. ソリューション

このエラーに対処する1つの方法は、OSレベルでスレッド制限の構成を増やすことです。

ただし、 OutOfMemoryError はプログラミングエラーを示している可能性があるため、これは理想的なソリューションではありません。 この問題を解決する他のいくつかの方法を見てみましょう。

4.1. エグゼキュータサービスフレームワークの活用

Javaのエグゼキュータサービスフレームワークをスレッド管理に活用することで、この問題にある程度対処できます。 デフォルトのエグゼキュータサービスフレームワーク、またはカスタムエグゼキュータ設定は、スレッドの作成を制御できます。

Executors#newFixedThreadPool メソッドを使用して、一度に使用できるスレッドの最大数を設定できます。

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

上記の例では、最初に5つのスレッドと実行可能なタスクを含む固定スレッドプールを作成し、スレッドを1時間待機させます。 次に、そのような10個のタスクをスレッドプールに送信し、5個のタスクがエグゼキュータサービスキューで待機していることを表明します。

スレッドプールには5つのスレッドがあるため、いつでも最大5つのタスクを処理できます。

4.2. スレッドダンプのキャプチャと分析

スレッドダンプのキャプチャと分析は、スレッドのステータスを理解するのに役立ちます。

サンプルのスレッドダンプを見て、何を学ぶことができるか見てみましょう。

上記のスレッドスナップショットは、前に示した例のJavaVisualVMからのものです。 このスナップショットは、継続的なスレッドの作成を明確に示しています。

継続的なスレッドの作成があることを確認したら、アプリケーションのスレッドダンプをキャプチャして、スレッドを作成しているソースコードを特定できます。

上記のスナップショットでは、スレッドの作成に関与するコードを特定できます。 これは、適切な対策を講じるための有用な洞察を提供します。

5. 結論

この記事では、 java .lang.OutOfMemoryError:新しいネイティブスレッドエラーを作成できませんでした。これは、Javaでの過度のスレッド作成が原因であることがわかりました。 ] 応用。

この問題に取り組むための2つの有用な手段として、 ExecutorService フレームワークとスレッドダンプ分析を検討することにより、エラーに対処して分析するためのいくつかのソリューションを検討しました。

いつものように、記事のソースコードはGitHubから入手できます。