argius note

プログラミング関連

Javaのシステムスレッドについて調べてみた (Java8)

システムスレッドについて、あまり知らなかったので、調べてみました。
それ以外にも、APIで生成されるスレッドや、スレッドグループについても、少し触れています。

※GrepCodeは2018年5月頃からサービスが利用できなくなっています。記事中のリンクはGrepCodeのものがそこそこ多いので、ご注意ください。


参考リンク


目次

  • はじめに
  • 実行環境
  • システムスレッドの名称と簡単な説明
  • APIを使用したときのスレッド
  • おわりに

はじめに

システムスレッドと、標準APIで生成されるスレッドについて、調べてみました。

それぞれのスレッドの説明には、どこでスレッドが生成されているのかをたどれるように、ソースコードを公開しているサイトへのリンク*1 を張っています。リンク先は、そのスレッドの名前が決まる場所にしています。

説明の内容は、参考リンク先の情報を元に作成していますが、まちがっていたり情報が古かったりする可能性がありますので、参考程度にお読みいただければと思います。


実行環境

  • Window7 professional 32bit
  • JDK8 (u25) i586

サンプルプログラム自体はプラットフォーム非依存ですが、一部のスレッド、特にAWT関連は、プラットフォームに依存する箇所がありますので、他の環境で確認したものもあります。


JDKに含まれているJava VisualVM(以降、VisualVMと表記)を使うと、スレッドやメモリーなどの情報をGUIインターフェイス上でリアルタイムに確認することができます。
例えば、スレッドダンプを出力するには、「アプリケーション」ペインに表示されているプロセスツリーのプロセスを右クリックし、「スレッドダンプ」を選択します。
VisualVMは、JDK6u7以降で利用できます。

参考URL:

システムスレッドの名称と簡単な説明

スレッド関連APIGUIを使わないアプリケーションを実行して採取したスレッドダンプを元に、基本的なシステムスレッドについて見ていきます。

スレッド名 所属グループ 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
  • 列の説明
    • 所属グループ: 所属しているThreadGroupの名称
    • daemon?: デーモンスレッドかどうか
    • JavaThread?: JavaThreadかどうか(C++上のクラス階層)
    • 可視?: ThreadGroup#listを実行した時に出力されるかどうか

順序は、スレッドダンプに出力される順番の反対に並べています。おそらくですが、スレッドが生成される順番です。


以下、それぞれのスレッドについて、簡単な説明をつけてみました。

VM Thread

VM自体のスレッド。ストップ・ザ・ワールド&FullGCを起動したり、スレッドダンプの受付などを行ったりするようです。

VM Periodic Task Thread

タイマー割り込みを担当するスレッドです。
C++のコード上では、クラス名がWatcherThreadとなっています。

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)

mainEventQueue.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"他


これ以外にも、使用するコンポーネントや機能によっては、別のスレッドが生成されたりします。(例: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


なお、JavaFXについては他のプラットフォームでは確認していませんので、プラットフォーム依存かどうかは分かりません。


おわりに

他にも、RMI関連のスレッドや、ツール関連---JDWP(Java Debug Wire Protocol)、JMXなど---のスレッドがありますが、全部網羅するのは大変なので、今回はここまで。


比較的単純なアプリケーションでも、裏方としてのスレッドがたくさん動いているんですね。
また少し、JVMの動作を知ることができました。

*1:JavaソースコードはOpenJDK8 b132のもの。C++ソースコードは、JDK8にリンクを張れるものが見つからなかったので、JDK7uを参照しています。

*2:try-catchを書かなくて良いように。