Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Should Your Thread Keep the JVM Alive?

2026年09月12日に「Dev.to」が公開したITニュース「Should Your Thread Keep the JVM Alive?」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

JVMは、全ての非デーモンスレッドが終了すると停止し、デーモンスレッドの実行状況は無視される。ユーザーの重要な作業など完了が必須な処理には、JVMを維持する非デーモンスレッドを使うべきだ。ログ収集やヘルスチェックなど途中で止まっても問題ない処理には、デーモンスレッドを使うと良い。

出典: Should Your Thread Keep the JVM Alive? | Dev.to公開日:

ITニュース解説

システムエンジニアを目指す初心者にとって、Java仮想マシン(JVM)におけるスレッドの種類、特にデーモンスレッドと非デーモンスレッドの違いを理解することは非常に重要である。この区別は一見すると些細な設定に思えるが、正しく理解していないと思わぬ問題に直面する可能性があるため、その本質を把握しておく必要がある。

JVMは、いつ自身を終了させるかについて一つのシンプルなルールを持っている。それは、「すべての非デーモンスレッドが終了したときにJVMは終了する」というものだ。デーモンスレッドは、このJVM終了の判断において完全に無視される。これがデーモンスレッドと非デーモンスレッドの唯一かつ最も重要な違いである。この一つのルールから、すべての挙動が派生する。言い換えれば、非デーモンスレッドはJVMを稼働させ続ける役割を持ち、デーモンスレッドにはその役割がない。最後の非デーモンスレッドが終了すると、JVMはデーモンスレッドが何をしているかに関わらず、そのプロセスを即座に終了させる。デーモンスレッドが処理の途中であろうと、ループの実行中であろうと、入出力操作の最中であろうと関係なく、強制的に終了するのだ。

デフォルトでは、プログラムで作成されるすべてのスレッドは非デーモンスレッドとして扱われる。プログラムの起動時に開始されるメインスレッドも非デーモンスレッドであり、そこから作成される他のスレッドも、特に指定がない限り親スレッドのデーモンステータスを引き継ぎ、非デーモンスレッドとなる。

このルールを具体的に示すコード例を考えてみよう。例えば、あるスレッドが10回繰り返してメッセージを表示し、各回で500ミリ秒待機する処理を行うとする。

1fun main() {
2    val worker = Thread {
3        repeat(10) {
4            println("Working... $it")
5            Thread.sleep(500)
6        }
7        println("Done!")
8    }
9
10    worker.apply {
11      isDaemon = false  // 非デーモン(デフォルト)
12      start()
13    }
14
15    println("main() returning")
16}

このコードを実行すると、main()関数自体はほとんどすぐに終了するメッセージを表示する。しかし、画面には「Working...」というメッセージが10回すべて表示され、「Done!」も出力されてから、最終的にJVMが終了する。これは、workerスレッドが非デーモンスレッドであるため、JVMがその作業スレッドの完了を待ってからシャットダウンした結果である。

次に、このコードのたった一行だけを変更してみる。

1worker.isDaemon = true   // デーモン

この変更を加えると、main()関数が終了するとすぐに「main() returning」と表示され、その直後にJVMはプロセスを終了させる。結果として、「Working...」のメッセージはほとんど表示されないか、運が良くてもせいぜい1回か2回しか表示されないだろう。デーモンスレッドであるworkerは、JVMを生き続けさせる他の非デーモンスレッドが存在しないため、処理の途中で強制的に終了させられてしまうのだ。

それでは、どのような場合にそれぞれのスレッドタイプを使用すべきか。

非デーモンスレッドは、その作業が失われると問題が発生する場合に利用する。例えば、ユーザーが実行を指示したアプリケーションの主要な処理は非デーモンスレッドにするべきだ。ユーザーが「メッセージ送信」ボタンをタップし、アプリケーションがHTTP POSTリクエストの途中でメインスレッドが終了してしまった場合、JVMが停止してリクエストが破棄されるような事態は避けたい。非デーモンスレッドが提供する「作業の完了を待つ」という特性は、このような状況で不可欠となる。一般的なネットワーク通信を行うスレッドプールなども、ユーザーの操作が関わる場合は非デーモンスレッドで構成されるべきである。

一方、デーモンスレッドは、その作業がオプションである、ベストエフォートである、あるいはバックグラウンド専用であるといった場合に使用する。デーモンスレッドは、メインアプリケーションの機能に従属するような作業に適している。メインアプリケーションが終了すれば、その従属的な作業は概念的に無関係となり、途中で終了させられても問題ない場合に利用する。

具体的なシナリオで考えてみよう。

シナリオ1:アナリティクスデータアップローダー。 アプリケーションの使用状況を収集し、30秒ごとにまとめてサーバーへアップロードするアナリティクスライブラリがあると仮定する。ユーザーがアプリを閉じようとしたときに、ちょうどデータのアップロード中だった場合、どうすべきだろうか。 アップローダーが非デーモンスレッドであった場合、JVMは現在のアップロードが完了するまで頑固に待機してから終了する。ユーザーから見れば、アプリを閉じたはずなのにプロセスが残っており、バッテリーを消費し、ネットワーク接続を維持し続けることになる。ネットワークが遅ければ数秒、エンドポイントがダウンしてタイムアウト待ちになれば数分かかる可能性もある。さらに悪いことに、アップローダーが永遠にデータを収集し続けるようなポーリングループを持っていた場合、JVMは決して終了せず、OSが強制終了するまでプロセスが残り続けることになる。 アップローダーがデーモンスレッドであった場合、アプリが閉じられると、アップローダーはアップロードの途中で強制終了され、JVMはきれいに終了する。このとき、進行中のデータバッチは失われるが、アナリティクスにとってはこれが適切なトレードオフである。ユーザーがボタンをタップしたというイベントを一つ失うことは許容できるが、ユーザーのアプリ終了をブロックしてまでそのデータを保存しようとするのは適切ではない。この場合の原則は「作業の損失が、その作業を待つコストよりも許容できる」というもので、アナリティクスにおいてはほぼ常に当てはまる。

シナリオ2:ベストエフォートなテレメトリーやハートビート。 アプリケーションが60秒ごとにヘルスチェックのエンドポイントへpingを送り、クライアントが生きていることをバックエンドに通知するような場合も同様である。アプリがシャットダウンする状況であれば、そのクライアントはもはや「生きている」とは言えないため、最後にハートビートを送ることは無意味である。このようなスレッドは、アプリが終了するときに同時に終了してほしいので、デーモンが適切な選択となる。

シナリオ3:ファイルライター。 ユーザーのドキュメントをディスクに書き込むバックグラウンドスレッドがある場合を考えてみよう。これはデーモンスレッドにすべきだろうか。 答えは「絶対に違う」だ。もしJVMが書き込みの途中で終了してしまえば、ファイルは破損し、ユーザーの作業は失われてしまう。このスレッドは非デーモンスレッドでなければならず、JVMは書き込みが完了するまで待機してから終了する必要がある。

これらのシナリオから、デーモンスレッドと非デーモンスレッドを選択するための一般的な判断基準を導き出すことができる。「このスレッドが作業の途中でJVMが今すぐ終了した場合、それは悪いことか?」と自問してみるのが良いだろう。 もし答えが「はい、それは悪いことだ」であれば、そのスレッドは非デーモンスレッドにすべきである。これには、ユーザーの主要な作業、金融取引、永続ストレージへの書き込み、ユーザーが開始したネットワークAPI呼び出しなどが含まれる。 もし答えが「いいえ、問題ない」であれば、そのスレッドはデーモンスレッドにすべきである。これには、アナリティクスデータの収集、テレメトリーの送信、キャッシュの事前読み込み、バックグラウンドでのメトリック収集、次回の起動時に再度実行すれば良いようなプリフェッチ処理などが該当する。

特にAndroidプラットフォームにおける注意点もある。Androidでは、JVMの「すべての非デーモンスレッドが終了したときにJVMが終了する」というルールは技術的には適用されるが、実際にはAndroidランタイムが、アクティビティやサービスといったコンポーネントのライフサイクルやフォアグラウンドの状態に基づいてプロセスを維持する。さらに、システムはスレッドの実行状態に関わらず、いつでもプロセスを強制終了できる。 このため、AndroidではデーモンフラグはJVMのシャットダウンそのものよりも、関連する別の懸念事項に影響を与える場合が多い。それは、残存する非デーモンスレッドがプロセスをクリーンに回収できない状態を引き起こすことである。たとえば、アクティビティが破棄された後もスレッドを生き続けさせるような非デーモンスレッドプールは、そのアクティビティ全体(とそのコンテキスト、ビュー階層などすべて)をメモリ上にリークさせ、ガベージコレクションによる回収を妨げる可能性がある。しかし、基本的な原則は変わらない。ユーザーによって開始された作業は非デーモンスレッド、バックグラウンドやオプションの作業はデーモンスレッドと考えるのが適切である。

関連コンテンツ

関連IT用語