2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jakarta EE 環境 (GlassFish) でJFRを使ってみる

Last updated at Posted at 2025-12-16

この記事は、Jakarta EE / Java EE Advent Calendar 2025 の 17 日目の記事です。

「Javaアプリのトラブル調査で、より詳細な情報を採取したい。」
「それも、ラクに設定できて、性能影響が抑えられる方法はないだろうか。」

そんなときは、JDK Flight Recorder (JFR) を試してみるとよいかもしれません。

JDK Flight Recorder (JFR) とは

JDK Flight Recorder (JFR) は、Java VM プロセスに関する詳細情報を収集できる、JDKの標準機能です。
低負荷で詳細な情報を採取できる と言われています。

Java SE アプリでJFRを有効にしてみる

以下のように、処理に1 - 1000 ms程度かかる RandomSleep.java があるとします。

import java.util.Random;

public class Main {

    public static void main(String... args) {
        RandomSleep proc = new RandomSleepImpl();
        proc.execute();
    }

    interface RandomSleep {
        void execute();
    }

    public static class RandomSleepImpl implements RandomSleep {
        public void execute() {
            // Random number between 1 and 1000
            Random random = new Random();
            int randomNumber = random.nextInt(1000) + 1;
            try {
                System.out.println("Sleep for " + randomNumber + " ms.");
                Thread.sleep(randomNumber);
            } catch (InterruptedException e) {
            }
        }
    }

}
# java RandomSleep.java
Sleep for 87 ms.

このアプリでJFRを有効にするには、-XX:StartFlightRecording:filename=<filepath> オプションを追加して実行するだけで実現できます。

# java -XX:StartFlightRecording:filename=/work/act-oss/demo-jfr-se/ RandomSleep.java
[0.389s][info][jfr,startup] Started recording 1. No limit specified, using maxsize=250MB as default.
[0.389s][info][jfr,startup] 
[0.389s][info][jfr,startup] Use jcmd 2936686 JFR.dump name=1 to copy recording data to file.
Sleep for 234 ms.

Java VM プロセスが終了すると、指定のパスにJFRログファイル(.jfr)が出力されます。

# find /work/act-oss/demo-jfr-se/ -name "*.jfr"
/work/act-oss/demo-jfr-se/hotspot-pid-2936686-id-1-2025_12_10_16_16_04.jfr

JFRログファイルの中には、以下のように様々な情報が集まっています。

work-1-1.png

JFRログファイルは、上記のように JDK Mission Control (JMC) を用いてGUIで可視化したり、jfr print コマンドで情報を出力したりして、参照できます。

Jakarta EE アプリでJFRを有効にしてみる

以下のような Jakarta Servlet のアプリで、JFRを有効にしてみます。

package com.example.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/RandomSleepServlet")
public class RandomSleepServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.setContentType("text/plain; charset=UTF-8");
        PrintWriter responseWriter = response.getWriter();

        RandomSleep proc = new RandomSleepImpl();
        proc.execute();
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        doGet(request, response);
    }

    interface RandomSleep {
        void execute();
    }

    public static class RandomSleepImpl implements RandomSleep {
        public void execute() {
            // Random number between 1 and 1000
            Random random = new Random();
            int randomNumber = random.nextInt(1000) + 1;
            try {
                System.out.println("Sleep for " + randomNumber + " ms.");
                Thread.sleep(randomNumber);
            } catch (InterruptedException e) {
            }
        }
    }

}

このServletを含むWebアプリ(.war)を、Jakarta EEに準拠したランタイムOSSであるGlassFishで動かします。

以下の手順では、2つのJava VM プロセス(GlassFish Server クラスター配下のインスタンス)でアプリを動かします。
各インスタンスでは、HTTPリクエストをポート28080/28081で受け付ける設定となっています。

# asadmin start-domain
...
Successfully started the domain : domain1
domain  Location: /opt/glassfish8/glassfish/domains/domain1
Log File: /opt/glassfish8/glassfish/domains/domain1/logs/server.log
Admin Port: 4,848
Command start-domain executed successfully.

# asadmin create-cluster cluster-1
Command create-cluster executed successfully.

# asadmin create-local-instance --cluster cluster-1 cluster-1-1
...
HTTP_LISTENER_PORT=28080
...
Command create-local-instance executed successfully.

# asadmin create-local-instance --cluster cluster-1 cluster-1-2
...
HTTP_LISTENER_PORT=28081
...
Command create-local-instance executed successfully.

# asadmin deploy --target cluster-1 RandomSleepWebApp.war 
Application deployed with name RandomSleepWebApp.
Command deploy executed successfully.

このとき、JFRを有効にするには、Java SE アプリ同様の設定を追加して起動するだけで実現できます。

# asadmin create-jvm-options --target cluster-1 -XX\\\:StartFlightRecording=filename\\\=\\\$\\\{com.sun.aas.instanceRoot\\\}/logs
Created 1 option(s)
Command create-jvm-options executed successfully.

# asadmin start-cluster cluster-1
Command start-cluster executed successfully.              

# for i in {1..10}; do for port in 28080 28081; do \
    curl http://localhost:${port}/RandomSleepWebApp/RandomSleepServlet; \
  done; done

Java VM プロセスが終了すると、指定のパスにJFRログファイル(.jfr)が出力されます。

# asadmin stop-cluster cluster-1
Command stop-cluster executed successfully.                 

# find /opt/glassfish8/glassfish/nodes/localhost-domain1/ -name "*.jfr"
/opt/glassfish8/glassfish/nodes/localhost-domain1/cluster-1-1/logs/hotspot-pid-3430092-id-1-2025_12_11_18_41_11.jfr
/opt/glassfish8/glassfish/nodes/localhost-domain1/cluster-1-2/logs/hotspot-pid-3430080-id-1-2025_12_11_18_41_11.jfr

以下のように、GlassFishのサーバーログ(server.log)と照らし合わせつつ、JFRログファイルから詳細情報を参照できるようになります。

[2025-12-11T18:40:46.747188+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=56 _ThreadName=http-listener-1(1)] [levelValue: 800] [[
  Sleep for 407 ms.]]

[2025-12-11T18:40:48.064102+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=72 _ThreadName=http-listener-1(3)] [levelValue: 800] [[
  Sleep for 803 ms.]]

[2025-12-11T18:40:49.131526+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=74 _ThreadName=http-listener-1(5)] [levelValue: 800] [[
  Sleep for 483 ms.]]

[2025-12-11T18:40:50.466758+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=71 _ThreadName=http-listener-1(2)] [levelValue: 800] [[
  Sleep for 571 ms.]]

[2025-12-11T18:40:51.898990+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=73 _ThreadName=http-listener-1(4)] [levelValue: 800] [[
  Sleep for 625 ms.]]

[2025-12-11T18:40:53.435862+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=56 _ThreadName=http-listener-1(1)] [levelValue: 800] [[
  Sleep for 846 ms.]]

[2025-12-11T18:40:54.336978+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=72 _ThreadName=http-listener-1(3)] [levelValue: 800] [[
  Sleep for 808 ms.]]

[2025-12-11T18:40:56.154131+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=74 _ThreadName=http-listener-1(5)] [levelValue: 800] [[
  Sleep for 761 ms.]]

[2025-12-11T18:40:57.790818+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=71 _ThreadName=http-listener-1(2)] [levelValue: 800] [[
  Sleep for 983 ms.]]

[2025-12-11T18:40:59.406193+09:00] [GF 8.0.0-M14] [INFO] [] [jakarta.enterprise.logging.stdout] [tid: _ThreadID=73 _ThreadName=http-listener-1(4)] [levelValue: 800] [[
  Sleep for 914 ms.]]

work-1-2.png

JFRのチューニング

JFRでは、他にも様々なオプションを指定できます。
例えば、記録期間を限定したり、ファイルサイズを制限したりすることで、実行環境への影響を抑えられないか検討できます。

おわりに

JFRは、オプションを1つ追加するだけで利用できるため、非常にお手軽です。
Jakarta EE 実行環境の様々な情報を収集してくれます。

次回は、Jakarta EE環境でより応用の利くJFR活用方法として、自分でカスタマイズした情報をJFRログファイルに出力してみます。

付録

Jakarta EE 実行環境

# cat /etc/redhat-release 
Red Hat Enterprise Linux release 8.10 (Ootpa)

# java --version
openjdk 21.0.9 2025-10-21 LTS
OpenJDK Runtime Environment Temurin-21.0.9+10 (build 21.0.9+10-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.9+10 (build 21.0.9+10-LTS, mixed mode, sharing)

# asadmin version
Version = Eclipse GlassFish 8.0.0-M14 (commit: 66cb8e8b4c6ead7ac3e7705e330613b36818f161)
Command version executed successfully.

参考情報

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?