😇

JavaFXアプリで発生したメモリリークの原因と対策

に公開

🧠 この記事を読むとできるようになること

  • JavaFXアプリケーションで発生するメモリリークの原因を理解できる
  • VisualVMを使ったJVMのメモリ調査手順がわかる
  • メモリリークの検出と解消方法(コード例付き)を学べる

👤 対象読者

  • JavaFXを用いた業務アプリ開発経験がある中級以上のJavaエンジニア
  • JavaによるWebアプリケーション開発経験者で、クライアントサイドとの違いに興味がある方

事象

JavaFXを使った業務アプリケーションにおいて、長時間使用後に java.lang.OutOfMemoryError(OOME)が発生。

特に画面遷移を頻繁に行う場面で、アプリが極端に重くなり、最終的にクラッシュする現象を確認。


調査

VisualVMを用いて、問題が発生したJavaFXアプリケーションのJVMメモリ状況を監視・分析。

目的は以下の2点:

  • GCが効かずに残り続けているオブジェクトの特定
  • ヒープメモリのどこでリークが起きているかの構造的把握

調査手順(時系列)

  1. VisualVMでアプリに接続し、ヒープダンプを取得
  2. GCを手動実行し、GC後のヒープの状態を確認
  3. 画面遷移後のControllerインスタンスが複数残っていることを発見
  4. GC Rootからの参照チェーンを追跡し、どこから参照が切れていないのかを調査

調査結果

  • 遷移後に不要となった Scene, Controller, Node などのJavaFXオブジェクトが GCされずにヒープに残存
  • その原因は、イベントリスナーやプロパティバインディングが外れておらず、強参照が残っていたため

対応策

参照の明示的な破棄

// Controllerのdisposeメソッド内などで
node.setOnAction(null);
node = null;

イベントリスナーの解除

// リスナーを解除するサンプル
ChangeListener<String> listener = (obs, oldVal, newVal) -> System.out.println(newVal);
textField.textProperty().addListener(listener);

// 画面遷移やdispose時に解除
textField.textProperty().removeListener(listener);

カスタムControllerでの破棄処理

public abstract class DisposableController {
    public abstract void dispose();
}

public class MainPageController extends DisposableController {
    @Override
    public void dispose() {
        // 参照やリスナー解除処理を実装
    }
}

課題

  • 業務アプリケーションでは前画面の情報保持が要件になることが多い
  • そのため、画面遷移時にControllerやデータモデルの参照を完全に破棄できない
  • 一部の参照はあえて残す必要があるため、設計的に“解放できる範囲”を意識して分離する必要がある

Webアプリケーションとの比較:ライフサイクルの違い

JavaのWebアプリケーションでは、通常リクエストスコープやセッションスコープを通じてオブジェクトの寿命をコントロールでき、不要になった参照はGCの対象になりやすい構造です。

一方JavaFXのようなデスクトップアプリでは、明示的なライフサイクル管理が求められる場面が多く、イベントハンドラやバインディングが残ることで容易にリークが発生します。

この違いを意識して設計することが、UIアプリ開発における安定性向上のカギとなります。


代替案と評価

明示的GCボタンの設置

  • 一時的なメモリ圧迫を緩和するため、ユーザーが任意でGCを実行できるUIボタンを設置
Button gcButton = new Button("メモリ解放");
gcButton.setOnAction(e -> {
    // GCは強制ではなく、リクエストである点に注意
    System.gc();
});

評価と課題

  • 一時的には有効だが、根本的なメモリ管理の見直しが必要
  • 本来はGCに頼らず、不要参照を明示的に切る構造の見直しが望ましい

まとめ

  • JavaFXでは、画面遷移後に不要なオブジェクトがGC対象にならず、メモリリークが発生しやすい構造がある
  • VisualVMによって、GC Rootからの参照分析が可能
  • イベントリスナーやプロパティバインディングの解除は必須
  • 状況によっては明示的GCや参照設計の工夫が必要
  • Webアプリとの違いを理解し、画面ライフサイクルとオブジェクト参照の明示的な制御が重要

Discussion