JavaFX アプリケーションに二重起動を防止する仕組みを追加したところ、 プロセスが終了せずに残ってしまうようになってしまいました。
- これは Windows 環境の Java 11 + OpenJFX 11 で発生した事象です。他のプラットフォーム、 OpenJFX のバージョンによっては問題が起こらない可能性もあります。
アプリケーションは以下のような構造です。main
メソッドで二重起動かどうかを判定して、 すでに同じアプリケーションが起動している場合は何もせずに return
するようになっています。
MyApp.javaimport javafx.application.Application;
import javafx.stage.Stage;
public class MyApp extends Application {
public static void main(String[] args) {
if(二重起動をチェック) {
//同じアプリケーションが起動している場合は何もせずプロセスを終了する
return;
}
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
}
}
このとき、 後から起動したプロセスは Application.launch
メソッドも呼ばないし、 Application
の実装クラスのインスタンスも生成しません。本当に私は何もしていません。にもかかわらず、 後から起動したプロセスは main
メソッドを終了した後も残り続けてしまうのでした。
はじめは二重起動のチェック処理に副作用があるのではないかと疑いました。非デーモンスレッドを起動しているんじゃないか?と。
しかし、 結果は違いました。原因を絞り込もうとコードを小さくしていった結果、 以下のコードでもプロセスが残留してしまうことが分かりました。
MyApp.javaimport javafx.application.Application;
import javafx.stage.Stage;
public class MyApp extends Application {
public static void main(String[] args) {
System.out.println("Hello, World!!");
}
@Override
public void start(Stage stage) throws Exception {
}
}
ただ文字列を出力するだけのプログラムです。こんな単純なプログラムでさえも、 なぜか、 プロセスが終了せずに残ってしまいます。
非デーモンスレッドが悪さをしているに違いありません。とりあえず、 すべてのスレッドを表示してみましょう。
MyApp.javaimport javafx.application.Application;
import javafx.stage.Stage;
import java.util.Comparator;
public class MyApp extends Application {
public static void main(String[] args) {
System.out.println("Hello, World!!");
//すべてのスレッドを表示する
Thread.getAllStackTraces().keySet().stream()
.sorted(Comparator.comparing(Thread::getId))
.forEach(t -> System.out.println(
"Thread " + t.getId() + "¥t" + t.getName()));
}
@Override
public void start(Stage stage) throws Exception {
}
}
コマンドプロンプトC:¥temp>java MyApp Hello, World!! Thread 1 main Thread 2 Reference Handler Thread 3 Finalizer Thread 4 Signal Dispatcher Thread 5 Attach Listener Thread 11 Common-Cleaner Thread 12 QuantumRenderer-0 Thread 14 InvokeLaterDispatcher Thread 15 JavaFX Application Thread
なんてこった!
いつのまにか JavaFX Application Thread が生まれています。
どうやら javafx.application.Application
の実装クラスをメインクラスとして起動すると JavaFX アプリケーション ・ スレッドが勝手に起動してしまうようです。
…思い出しました。そういえば、 JavaFX アプリケーションでは main
メソッドを省略できるという話を聞いたことがあります。Application
実装クラスをメインクラスとして指定すると main
メソッドが定義されていないなくても、 JavaFX アプリケーションとして起動できるのだとか。
試してみましょう。main
メソッドを持たない小さな JavaFX アプリケーションを作成してみます。
MyAppimport javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class MyApp extends Application {
@Override
public void start(Stage stage) {
Button button = new Button("Hello, World!!");
Scene scene = new Scene(button);
stage.setScene(scene);
stage.show();
}
}
この MyApp
クラスをメインクラスとして起動してみると…
たしかに、 main
メソッドがなくても起動しました。
では、 main
メソッドを追加するとどうなるのでしょうか? JavaFX アプリケーションの main
メソッドでは launch
メソッドを呼ぶのが通例ですが、 ここではあえて何もしません。
MyAppimport javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class MyApp extends Application {
public static void main(String[] args) {
// do nothing
}
@Override
public void start(Stage stage) {
Button button = new Button("Hello, World!!");
Scene scene = new Scene(button);
stage.setScene(scene);
stage.show();
}
}
これを実行すると、 JavaFX アプリケーション ・ スレッドは生成されるが JavaFX アプリケーションは起動しないという中途半端な状態になりました。
「main
メソッドがなくても起動できる」 という仕組みと 「main
メソッドが launch
せずに終了する」 という振る舞いが競合しておかしくなっているみたいですね。
そもそも main
メソッドなしで起動できる仕組みなんて JavaFX に必要だったんでしょうか? このわずか 3 行が省略できるだけですよ。
public static void main(String[] args) {
launch(args);
}
なんだか、 main
メソッドなしで起動できる JavaFX の仕組みに振り回されてしまったようです。(何故 main
メソッドのない Java プログラムを開始できるのか今も仕組みを理解できてはいません…。)
回避策1
JavaFX アプリケーション ・ スレッドが起動していると分かれば話は簡単です。Platform.exit()
を呼び出して JavaFX アプリケーション ・ スレッドを終了させればいいのです。
MyApp.javaimport javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
public class MyApp extends Application {
public static void main(String[] args) {
if(二重起動をチェック) {
//同じアプリケーションが起動している場合は何もせずプロセスを終了する
//なぜかJavaFXアプリケーションスレッドが起動してしまうので終了させる
Platform.exit();
return;
}
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
}
}
これで、 プロセスが残留することはなくなりました。
しかし、 必要のない JavaFX アプリケーション ・ スレッドが勝手に起動して、 それを終了させなければならないというのもおかしな話です。
回避策 2
Application
実装クラスをメインクラスに指定すると JavaFX のおかしな仕組みが発動するので、 main
メソッドを定義したメインクラスを別のクラスとして分離すれば、 この問題は回避できるはずです。
Main.javaimport javafx.application.Application;
public class Main {
public static void main(String[] args) throws Exception {
if(二重起動をチェック) {
//同じアプリケーションが起動している場合は何もせずプロセスを終了する
return;
}
Application.launch(MyApp.class, args);
}
}
MyApp.javaimport javafx.application.Application;
import javafx.stage.Stage;
public class MyApp extends Application {
@Override
public void start(Stage stage) throws Exception {
}
}
上手くいきました。これで、 JavaFX アプリケーション ・ スレッドが勝手に起動しないようになりました。
- JavaFX は
main
メソッドがなくても起動できる。 - JavaFX で
main
メソッドを書くなら必ずlaunch
を呼ぶ。 - JavaFX の
main
メソッドでlaunch
を呼ばないことがあるなら
メインクラスはApplication
を継承しないようにする。
今日もまた少し JavaFX に詳しくなれたようです。