Javaのシステムスレッドについて調べてみた (Java8)
システムスレッドについて、あまり知らなかったので、調べてみました。
それ以外にも、APIで生成されるスレッドや、スレッドグループについても、少し触れています。
※GrepCodeは2018年5月頃からサービスが利用できなくなっています。記事中のリンクはGrepCodeのものがそこそこ多いので、ご注意ください。
参考リンク
- HotSpot Runtime Overview - OpenJDK
- JVM Internals (JamesDBloom - Blog)
- Java SE 8 API仕様
- GC: openjdk-8-b132.jar - GrepCode Java Project Source
- Mirror of OpenJDK repositories - GitHub
目次
- はじめに
- 実行環境
- システムスレッドの名称と簡単な説明
- APIを使用したときのスレッド
- おわりに
はじめに
システムスレッドと、標準APIで生成されるスレッドについて、調べてみました。
それぞれのスレッドの説明には、どこでスレッドが生成されているのかをたどれるように、ソースコードを公開しているサイトへのリンク*1 を張っています。リンク先は、そのスレッドの名前が決まる場所にしています。
説明の内容は、参考リンク先の情報を元に作成していますが、まちがっていたり情報が古かったりする可能性がありますので、参考程度にお読みいただければと思います。
実行環境
- Window7 professional 32bit
- JDK8 (u25) i586
サンプルプログラム自体はプラットフォーム非依存ですが、一部のスレッド、特にAWT関連は、プラットフォームに依存する箇所がありますので、他の環境で確認したものもあります。
JDKに含まれているJava VisualVM(以降、VisualVMと表記)を使うと、スレッドやメモリーなどの情報をGUIインターフェイス上でリアルタイムに確認することができます。
例えば、スレッドダンプを出力するには、「アプリケーション」ペインに表示されているプロセスツリーのプロセスを右クリックし、「スレッドダンプ」を選択します。
VisualVMは、JDK6u7以降で利用できます。
参考URL:
システムスレッドの名称と簡単な説明
スレッド関連APIやGUIを使わないアプリケーションを実行して採取したスレッドダンプを元に、基本的なシステムスレッドについて見ていきます。
スレッド名 | 所属グループ | daemon? | JavaThread? | 可視? |
---|---|---|---|---|
VM Thread | (なし?) | No | No | No |
VM Periodic Task Thread | (なし?) | No | No | No |
main | main | No | Yes | Yes |
Reference Handler | system | Yes | Yes | Yes |
Finalizer | system | Yes | Yes | Yes |
Signal Dispatcher | system | Yes | Yes | Yes |
Attach Listener | system | Yes | Yes | Yes |
CompilerThread | system | Yes | Yes | No |
Service Thread | system | Yes | Yes | No |
- 列の説明
順序は、スレッドダンプに出力される順番の反対に並べています。おそらくですが、スレッドが生成される順番です。
以下、それぞれのスレッドについて、簡単な説明をつけてみました。
main
main
メソッドを実行するスレッドです。
スレッドグループ"system"の子のスレッドグループ"main"に所属しています。
Reference Handler
「参照オブジェクト」を監視するスレッドで、java.lang.ref.Reference
のクラス初期化子で起動します。
Finalizer
Object#finalize
を実行するスレッドで、java.lang.ref.Finalizer
のクラス初期化子で起動します。
finalize
メソッドの中でThread.currentThread
を取得すれば、このスレッドで実行されていることが確認できます。
Signal Dispatcher
その名の通り、プラットフォームのシグナルをディスパッチするスレッドです。
スレッドダンプで使うCtrl+Breakの処理は、このスレッドが関係しています。
Attach Listener
ツールなどからの、JVMプロセスへのアタッチを受け付けるスレッドです。
起動オプションに-XX:+DisableAttachMechanism
を付けると、このスレッドが起動しません。プロセスにアタッチすることができなくなるか、アクセスできる情報が制限されます。例えば、VisualVMではプロセス自体は表示されますが、サンプラーが無効になります。
CompilerThread
実行時のネイティブコンパイルを行うスレッドです。
複数起動することもあり、"C1 CompilerThread#"と"C2 CompilerThread#"(#は数字)のようなスレッド名になります。
Service Thread
JVMTIエージェントなどを実行するスレッド。
Java6までは、"Low Memory Detector"という名前だったみたいです。
APIを使用したときのスレッドとスレッドグループ
スレッドに関係するAPIを使用した時に、どのようなスレッドが動いているのかを見てみましょう。
スレッドの確認は、スレッドダンプでは無く、ThreadGroup#list
を利用(下記メソッド)して出力します。
また、デバッグモードでない状態のプロセスで確認しています。
最初に、スレッドグループ一覧を出力するユーティリティーメソッドを宣言しておきます。
printThreadGroups
メソッドと、sleep
メソッド*2
static void printThreadGroups() { System.out.println("----------------------------------------"); System.out.printf("current thread = %s%n", Thread.currentThread()); Thread.currentThread().getThreadGroup().getParent().list(); System.out.println("----------------------------------------"); } static void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } }
printThreadGroups
は、スレッドグループのRootであるsystem
の子グループから実行されることを想定しています。main
スレッドが所属するスレッドグループmain
は、system
の子です。
トップや孫のスレッドグループから呼ぶと、うまく行きません。
このメソッドを使って、スレッドグループを見ていきましょう。
main
起動直後のシステムスレッドグループ
printThreadGroups()
だけを書いたmain
メソッドを実行した結果です。
(current thread = Thread[main,5,main]) java.lang.ThreadGroup[name=system,maxpri=10] Thread[Reference Handler,10,system] Thread[Finalizer,8,system] Thread[Signal Dispatcher,9,system] Thread[Attach Listener,5,system] java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main]
スレッドダンプで見たもの以外はありませんね。
スレッドグループの親子関係
今回の趣旨とは少し外れますが、スレッドグループの親子関係について見てみます。
ThreadGroup systemThreadGroup = Thread.currentThread().getThreadGroup().getParent(); ThreadGroup child = new ThreadGroup(systemThreadGroup, "別の子"); ThreadGroup gchild1 = new ThreadGroup("新しい孫"); ThreadGroup gchild2 = new ThreadGroup(child, "別の子の子(別の孫)"); printThreadGroups();
- 結果
------------------------------------------------------------ (current thread = Thread[main,5,main]) java.lang.ThreadGroup[name=system,maxpri=10] Thread[Reference Handler,10,system] Thread[Finalizer,8,system] Thread[Signal Dispatcher,9,system] Thread[Attach Listener,5,system] java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] java.lang.ThreadGroup[name=新しい孫,maxpri=10] java.lang.ThreadGroup[name=別の子,maxpri=10] java.lang.ThreadGroup[name=別の子の子(別の孫),maxpri=10] ------------------------------------------------------------
親のスレッドグループをsystem
にした場合、main
スレッドグループとは別のスレッドグループが作られているのが分かります。
また、親のスレッドグループを指定しない場合、実行しているスレッドが所属しているのと同じスレッドグループになります。
マルチスレッド系APIを使った時のスレッド
スレッドの単独起動と、Concurrent Utilitiesのスレッドプールを使ってスレッドを起動した場合です。
// import java.util.*; // import java.util.concurrent.*; Runnable task = new Runnable() { @Override public void run() { sleep(1000L); } }; // Java8なら task = () -> { sleep(1000L); }; でもOK new Thread(task).start(); Executors.newSingleThreadExecutor().execute(task); ForkJoinPool.commonPool().execute(task); printThreadGroups();
- 結果
------------------------------------------------------------ (current thread = Thread[main,5,main]) java.lang.ThreadGroup[name=system,maxpri=10] Thread[Reference Handler,10,system] Thread[Finalizer,8,system] Thread[Signal Dispatcher,9,system] Thread[Attach Listener,5,system] java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[Thread-0,5,main] Thread[pool-1-thread-1,5,main] Thread[ForkJoinPool.commonPool-worker-1,5,main] ------------------------------------------------------------
ここではスレッドの命名規則が変わるだけですね。
AWTスレッドと関連するスレッド
EventQueue.invokeLater
を処理して呼び出しmain
起動直後のスレッドグループ一覧を見てみます。
※Swingだと説明が冗長になるので、あえてAWTを使っています。
// import java.awt.*; EventQueue.invokeLater(new Runnable() { @Override public void run() { sleep(1000L); Frame f = new Frame(); f.setVisible(true); printThreadGroups(); // (2) sleep(3000L); System.exit(0); } }); printThreadGroups(); // (1)
main
がEventQueue.invokeLater
を呼び出した直後と、java.awt.Frame#setVisible
を呼び出した直後でスレッドグループを表示しています。
- 結果
------------------------------------------------------------ (current thread = Thread[main,5,main]) java.lang.ThreadGroup[name=system,maxpri=10] Thread[Reference Handler,10,system] Thread[Finalizer,8,system] Thread[Signal Dispatcher,9,system] Thread[Attach Listener,5,system] Thread[Java2D Disposer,10,system] Thread[AWT-Shutdown,5,system] Thread[AWT-Windows,6,system] java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[AWT-EventQueue-0,6,main] ------------------------------------------------------------ ------------------------------------------------------------ (current thread = Thread[AWT-EventQueue-0,6,main]) java.lang.ThreadGroup[name=system,maxpri=10] Thread[Reference Handler,10,system] Thread[Finalizer,8,system] Thread[Signal Dispatcher,9,system] Thread[Attach Listener,5,system] Thread[Java2D Disposer,10,system] Thread[AWT-Shutdown,5,system] Thread[AWT-Windows,6,system] java.lang.ThreadGroup[name=main,maxpri=10] Thread[AWT-EventQueue-0,6,main] Thread[DestroyJavaVM,5,main] ------------------------------------------------------------
AWT固有のスレッドがいくつか登場します。
DestroyJavaVMは、AWTとは関係なく、main
スレッドが終了した際に呼ばれ、デーモンスレッド以外のスレッドがすべて終了した時に、JVMを終了させるためのスレッドです。
AWT-Windowsは、プラットフォームによって異なります。
Windows以外でいくつか確認してみました。
- AppleJVM(Java6): "AWT-AppKit"
- MacOSXでビルドしたOpenJDK(Java7): "AppKit Thread"
- Linux(Linux Mint)のOpenJDK(Java7): "AWT-XAWT"他
java/awt/EventQueue.java:181
(openjdk8 b132) - GrepCode Class Sourcesun/java2d/Disposer.java:91
(openjdk8 b132) - GrepCode Class Sourcesun/awt/AWTAutoShutdown.java:336
(openjdk8 b132) - GrepCode Class Source- AWT(main)
これ以外にも、使用するコンポーネントや機能によっては、別のスレッドが生成されたりします。(例:Image Fetcher, D3D Screen Updater)
JavaFXに関連するスレッド
JavaFXでウィンドウを表示した時のスレッドを見てみます。適当なFXML
(テーブルとボタンを含む)を作って、ウィンドウを表示します。
import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public final class App extends Application { static String FXML_NAME = ... ; @Override public void start(Stage stage) throws Exception { Parent fxml = FXMLLoader.load(getClass().getResource(FXML_NAME + ".fxml")); stage.setScene(new Scene(fxml)); stage.show(); sleep(1000L); printThreadGroups(); } public static void main(String[] args) { launch(); } }
- 結果
---------------------------------------- (current thread = Thread[JavaFX Application Thread,5,main]) java.lang.ThreadGroup[name=system,maxpri=10] Thread[Reference Handler,10,system] Thread[Finalizer,8,system] Thread[Signal Dispatcher,9,system] Thread[Attach Listener,5,system] Thread[Prism Font Disposer,10,system] java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[JavaFX-Launcher,5,main] Thread[QuantumRenderer-0,5,main] Thread[Thread-2,5,main] Thread[JavaFX Application Thread,5,main] Thread[Thread-3,5,main] ----------------------------------------
JavaFX固有と思われるものが、下記のスレッドです。Thread-2とThread-3は名前からでは判断できませんが、JavaFX内部で呼ばれているものだと思います。
Prism Font Disposer
JavaFX-Launcher
QuantumRenderer
JavaFX Application Thread
Thread-2
Thread-3
com/sun/javafx/font/Disposer.java:67
(openjfx 1.8.0-ea-b96.1) - GrepCode Class Sourcecom/sun/javafx/tk/quantum/QuantumRenderer.java:145
(openjfx 1.8.0-ea-b96.1) - GrepCode Class Sourcecom/sun/javafx/application/LauncherImpl.java:172
(openjfx 1.8.0-ea-b96.1) - GrepCode Class Sourcecom/sun/javafx/tk/quantum/QuantumToolkit.java:423
(openjfx 1.8.0-ea-b96.1) - GrepCode Class Source
なお、JavaFXについては他のプラットフォームでは確認していませんので、プラットフォーム依存かどうかは分かりません。