CLOVER🍀

That was when it all began.

RESTEasy+Vert.x(Embedded Container)で遊ぶ

これは、なにをしたくて書いたもの?

RESTEasyを組み込みサーバー上で動かす方法はいくつかあるのですが、以前にもUndertow、Netty、JDK HTTP Severなどで
やったことがあります。

RESTEasyをJDK付属のHTTPサーバ、Undertowで動かす - CLOVER🍀

UndertowでJAX-RS(RESTEasy)とCDIを使う - CLOVER🍀

RESTEasy+Netty(Netty 3/Netty 4/Netty 4 with CDI)で遊ぶ - CLOVER🍀

今回は、Vert.xを試してみようかなと。Quarkusで使われているJAX-RSのデフォルトのエンジンも、UndertowからVert.xに
なりましたしね。

RESTEasy Vert.x Embedded Container

RESTEasyでの、Vert.xをEmbedded Containerとして組み込む方法は、こちら。

Vert.x

Undertowなど、他のEmbedded Containerを使ったことがあれば、そう難しくはありません。

JAX-RSクライアント側の実装もあるのですが、こちらは今回のお題では問題が出たのでパスしました…。

Vertx Client Engine

とりあえず、使っていってみましょう。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.7 2020-04-14
OpenJDK Runtime Environment (build 11.0.7+10-post-Ubuntu-2ubuntu218.04)
OpenJDK 64-Bit Server VM (build 11.0.7+10-post-Ubuntu-2ubuntu218.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.7, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.15.0-101-generic", arch: "amd64", family: "unix"

準備

pom.xmlは、以下のように設定。

    <dependencies>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-vertx</artifactId>
            <version>4.5.3.Final</version>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
            <version>3.9.1</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.6.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.6.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.assertj</groupId>
          <artifactId>assertj-core</artifactId>
          <version>3.16.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

resteasy-vertxではVert.xへの依存関係はprovidedとして定義されているため、Vert.xに関しては自分で依存関係に加えてあげないと
いけません。

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-vertx</artifactId>
            <version>4.5.3.Final</version>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
            <version>3.9.1</version>
        </dependency>

なお、providedに設定されているVert.xのバージョンは、こちらですね。

https://github.com/resteasy/Resteasy/blob/4.5.3.Final/resteasy-dependencies-bom/pom.xml#L34

その他の依存関係(JUnit、AssertJ)は、テストコード用として使います。

では、プログラムを書いていってみましょう。

JAX-RSリソースクラス

まずは、JAX-RSリソースクラスを用意します。簡単な、足し算を行うだけのクラスです。
src/main/java/org/littlewings/resteasy/vertx/CalcResource.java

package org.littlewings.resteasy.vertx;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("calc")
public class CalcResource {
    @GET
    @Path("add")
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.TEXT_PLAIN)
    public int add(@QueryParam("a") int a, @QueryParam("b") int b) {
        return a + b;
    }
}

テストコードの雛形

Vert.xを使ったサーバーの起動および、動作確認はテストコードで行います。

テストコードの雛形は、こちらです。
src/test/java/org/littlewings/reteasy/vertx/ResteasyVertxTest.java

package org.littlewings.reteasy.vertx;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.UriBuilder;

import io.vertx.core.VertxOptions;
import io.vertx.core.http.HttpServerOptions;
import org.jboss.resteasy.plugins.server.vertx.VertxJaxrsServer;
import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.junit.jupiter.api.Test;
import org.littlewings.resteasy.vertx.CalcResource;

import static org.assertj.core.api.Assertions.assertThat;

public class ResteasyVertxTest {

    // ここに、テストを書く!!
}

シンプルに使ってみる

最初は、1番シンプルなパターンで使ってみましょう。

こんな感じです。

    @Test
    public void gettingStarted() {
        ResteasyDeployment deployment = new VertxResteasyDeployment();
        deployment.getResources().add(new CalcResource());

        VertxJaxrsServer server = new VertxJaxrsServer();
        server.setDeployment(deployment);
        server.setPort(8080);
        server.setRootResourcePath("");

        server.start();

        try {
            Client client =
                    ClientBuilder.newBuilder()
                            .build();

            int result =
                    client
                            .target(UriBuilder.fromUri("http://localhost:8080/calc/add?a={a}&b={b}").build(5, 3))
                            .request()
                            .get(Integer.class);

            assertThat(result).isEqualTo(8);

            client.close();
        } finally {
            server.stop();
        }
    }

ResteasyDeploymentには、Applicationのサブクラスや、JAX-RSリソースクラスなどを設定します。今回は、作成した
リソースクラスのみです。

        ResteasyDeployment deployment = new VertxResteasyDeployment();
        deployment.getResources().add(new CalcResource());

サーバーを起動するには、VertxJaxrsServerクラスを使用します。こちらで、パスやポートの設定を行い、先ほど作成した
ResteasyDeploymentを使用してJAX-RSサーバーとしての設定を行います。

        VertxJaxrsServer server = new VertxJaxrsServer();
        server.setDeployment(deployment);
        server.setPort(8080);
        server.setRootResourcePath("");

        server.start();

あとは、startするだけ。

終了時には、stopメソッドを呼び出せばOKです。

        } finally {
            server.stop();
        }

確認は、JAX-RSクライアントで。

            Client client =
                    ClientBuilder.newBuilder()
                            .build();

            int result =
                    client
                            .target(UriBuilder.fromUri("http://localhost:8080/calc/add?a={a}&b={b}").build(5, 3))
                            .request()
                            .get(Integer.class);

            assertThat(result).isEqualTo(8);

            client.close();

まずは、これで確認できました、と。

Vert.xの設定を行う

次に、もうちょっと細かくVert.xの設定を行ってみましょう。

こちらです。

    @Test
    public void customizeVertx() {
        ResteasyDeployment deployment = new VertxResteasyDeployment();
        deployment.getResources().add(new CalcResource());

        VertxOptions vertxOptions = new VertxOptions();
        vertxOptions.setEventLoopPoolSize(Runtime.getRuntime().availableProcessors() * 2);  // default
        vertxOptions.setWorkerPoolSize(30);

        HttpServerOptions serverOptions = new HttpServerOptions();
        serverOptions.setMaxHeaderSize(8192);
        serverOptions.setPort(8080);
        serverOptions.setReuseAddress(true);

        VertxJaxrsServer server = new VertxJaxrsServer();
        server.setDeployment(deployment);
        // server.setPort(8080);
        server.setRootResourcePath("");
        server.setVertxOptions(vertxOptions);
        server.setServerOptions(serverOptions);

        server.start();

        try {
            Client client =
                    ClientBuilder.newBuilder()
                            .build();

            int result =
                    client
                            .target(UriBuilder.fromUri("http://localhost:8080/calc/add?a={a}&b={b}").build(5, 3))
                            .request()
                            .get(Integer.class);

            assertThat(result).isEqualTo(8);

            client.close();
        } finally {
            server.stop();
        }
    }

スレッド数の設定などは、VertxOptionsで行います。

        VertxOptions vertxOptions = new VertxOptions();
        vertxOptions.setEventLoopPoolSize(Runtime.getRuntime().availableProcessors() * 2);  // default
        vertxOptions.setWorkerPoolSize(30);

また、HTTPサーバーとしての設定は、HttpServerOptionsで行います。

        HttpServerOptions serverOptions = new HttpServerOptions();
        serverOptions.setMaxHeaderSize(8192);
        serverOptions.setPort(8080);
        serverOptions.setReuseAddress(true);

こちらを、VertxJaxrsServerに設定します。

        VertxJaxrsServer server = new VertxJaxrsServer();
        server.setDeployment(deployment);
        // server.setPort(8080);
        server.setRootResourcePath("");
        server.setVertxOptions(vertxOptions);
        server.setServerOptions(serverOptions);

VertxOptionsやHttpServerOptionsは、Vert.x自体が提供するクラスです。

VertxJaxrsServer#setPortなどは、実はVertxJaxrsServerが内部的に持っているHttpServerOptionsへ設定しているだけなので、
直接HttpServerOptionsを使う時は、設定はこちらに任せた方がよいのかもしれません。

JAX-RSクライアントを使った確認は、先ほどと同じなので省略。

あと、今回はRESTEasyを動かすためのEmbedded ContainerとしてVert.xを使用しましたが、Vert.xにRESTEasyを組み込む
という方法もあるようです(パスしましたが)。

クライアント側は、今回の確認パターンでは問題があったので…ちょっとなんとかしたいですね。

jcmd(jstack)を使わずに、スレッドダンプを取得する

これは、なにをしたくて書いたもの?

jcmdやjstackなどを使わずに、Javaアプリケーションのスレッドダンプを取得する方法を、自分でも試しておこうかなと。

ThreadMXBean#dumpAllThreads​とThread#getAllStackTraces

やり方としては、ThreadMXBeanやThreadクラスを利用すればよさそうです。

ThreadMXBeanを使った例としては、Spring Boot Actuatorのスレッドダンプ用のエンドポイントがこちらを使っています。

https://github.com/spring-projects/spring-boot/blob/v2.3.0.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpoint.java

ThreadMXBeanのJavadocは、こちら。

ThreadMXBean (Java SE 11 & JDK 11 )

また、GraalVMのSubstrate VMではJMXが使えないので、Threadクラスから全スレッドのStackTraceElementを取得するという
方法もあります。

こちらを使っているのは、Quarkusですね。SignalHandlerを使って、スレッドダンプを取れるようにしてあります。

https://github.com/quarkusio/quarkus/blob/1.5.0.Final/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java

https://github.com/quarkusio/quarkus/blob/1.5.0.Final/core/runtime/src/main/java/io/quarkus/runtime/graal/DiagnosticPrinter.java

ThreadのJavadocは、こちら。

Thread (Java SE 11 & JDK 11 )

このあたりを使っていってみましょう。

環境

今回の環境は、こちら。

$ java --version
openjdk 11.0.7 2020-04-14
OpenJDK Runtime Environment (build 11.0.7+10-post-Ubuntu-2ubuntu218.04)
OpenJDK 64-Bit Server VM (build 11.0.7+10-post-Ubuntu-2ubuntu218.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.7, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.15.0-101-generic", arch: "amd64", family: "unix"

準備

お題としては、JAX-RSリソースクラスに、スレッドダンプをJSONで返すようなプログラムを書いてみたいと思います。

JAX-RSの実装としては、RESTEasyを使用しましょう。

pom.xmlの依存関係は、こちら。

    <dependencies>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-vertx</artifactId>
            <version>4.5.3.Final</version>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
            <version>3.9.1</version>
        </dependency>

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson2-provider</artifactId>
            <version>4.5.3.Final</version>
        </dependency>
    </dependencies>

サンプルコード

作成したソースコードを載せていきます。

まずは、スレッドダンプを取得するJAX-RSリソースクラス。
src/main/java/org/littlewings/resteasy/ThreadDumpResource.java

package org.littlewings.resteasy;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("thread")
public class ThreadDumpResource {
    @GET
    @Path("mx-bean")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> threadMXBean() {
        ThreadInfo[] threadInfoArray = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);

        return Map.of("thread_dump", threadInfoArray);
    }

    @GET
    @Path("all-threads-stacktraces")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> allThreadsStackTraces() {
        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();

        return allStackTraces
                .entrySet()
                .stream()
                .collect(Collectors.toMap(e -> e.getKey().getName(), e -> {
                    Thread thread = e.getKey();
                    return Map.of(
                            "thread_info",
                            Map.of("id", thread.getId(), "priority", thread.getPriority(), "state", thread.getState(), "group", thread.getThreadGroup(), "alive", thread.isAlive(), "daemon", thread.isDaemon()),
                            "stacktraces",
                            e.getValue()
                    );
                }));
    }
}

こちらが、ThreadMXBeanを使っている方ですね。ThreadMXBean#dumpAllThreadsメソッドを使用します。

    @GET
    @Path("mx-bean")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> threadMXBean() {
        ThreadInfo[] threadInfoArray = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);

        return Map.of("thread_dump", threadInfoArray);
    }

2つの引数の意味は、ロックモニターを出力するかどうかと、ロックされたすべてのシンクロナイザーを出力するかどうかです。

https://docs.oracle.com/javase/jp/11/docs/api/java.management/java/lang/management/ThreadMXBean.html#dumpAllThreads(boolean,boolean)

ThreadMXBean#dumpAllThreadsメソッドの戻り値は、ThreadInfoの配列です。ここから、スレッドの情報やスタックトレースなどを
取得することができます。

ThreadInfo (Java SE 11 & JDK 11 )

もうひとつは、Thread#getAllStackTracesを使う方です。

    @GET
    @Path("all-threads-stacktraces")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> allThreadsStackTraces() {
        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();

        return allStackTraces
                .entrySet()
                .stream()
                .collect(Collectors.toMap(e -> e.getKey().getName(), e -> {
                    Thread thread = e.getKey();
                    return Map.of(
                            "thread_info",
                            Map.of("id", thread.getId(), "priority", thread.getPriority(), "state", thread.getState(), "group", thread.getThreadGroup(), "alive", thread.isAlive(), "daemon", thread.isDaemon()),
                            "stacktraces",
                            e.getValue()
                    );
                }));
    }

こちらは、全スレッドに対して、Threadをキー、StackTraceElementの配列を値にしたMapを返します。

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Thread.html#getAllStackTraces()

今回のお題ではこのMapをそのままJacksonでJSONに変換してもらってもいいのですが、それだとThread.Stateなどが
出力されないので、ちょっとだけ整形しておきました。

        return allStackTraces
                .entrySet()
                .stream()
                .collect(Collectors.toMap(e -> e.getKey().getName(), e -> {
                    Thread thread = e.getKey();
                    return Map.of(
                            "thread_info",
                            Map.of("id", thread.getId(), "priority", thread.getPriority(), "state", thread.getState(), "group", thread.getThreadGroup(), "alive", thread.isAlive(), "daemon", thread.isDaemon()),
                            "stacktraces",
                            e.getValue()
                    );
                }));

あとは、JAX-RSサーバーを起動するクラスを用意するだけです。
src/main/java/org/littlewings/resteasy/Server.java

package org.littlewings.resteasy;

import java.util.List;

import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.vertx.VertxJaxrsServer;
import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment;
import org.jboss.resteasy.spi.ResteasyDeployment;

public class Server {
    public static void main(String... args) {
        Logger logger = Logger.getLogger(Server.class);

        ResteasyDeployment deployment = new VertxResteasyDeployment();
        deployment.setResources(List.of(new ThreadDumpResource()));

        VertxJaxrsServer server = new VertxJaxrsServer();
        server.setDeployment(deployment);
        server.setRootResourcePath("");
        server.setPort(8080);

        server.start();

        logger.infof("Server startup");

        System.console().readLine("> Press Enter stop.");

        server.stop();
    }
}

確認する

それでは、確認してみましょう。

まずは、作成したサーバーを起動。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.resteasy.Server

ThreadMXBeanの方から確認してみます。

$ curl -s localhost:8080/thread/mx-bean | jq

結果は、こんな感じ。

{
  "thread_dump": [
    {
      "threadName": "main",
      "threadId": 1,
      "blockedTime": -1,
      "blockedCount": 0,
      "waitedTime": -1,
      "waitedCount": 1,
      "lockName": "java.lang.Thread@411755fc",
      "lockOwnerId": -1,
      "lockOwnerName": null,
      "daemon": false,
      "inNative": false,
      "suspended": false,
      "threadState": "WAITING",
      "priority": 5,
      "stackTrace": [
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "wait",
          "fileName": "Object.java",
          "lineNumber": -2,
          "className": "java.lang.Object",
          "nativeMethod": true
        },
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "join",
          "fileName": "Thread.java",
          "lineNumber": 1305,
          "className": "java.lang.Thread",
          "nativeMethod": false
        },

〜省略〜

    {
      "threadName": "vert.x-eventloop-thread-7",
      "threadId": 24,
      "blockedTime": -1,
      "blockedCount": 19,
      "waitedTime": -1,
      "waitedCount": 0,
      "lockName": null,
      "lockOwnerId": -1,
      "lockOwnerName": null,
      "daemon": false,
      "inNative": false,
      "suspended": false,
      "threadState": "RUNNABLE",
      "priority": 5,
      "stackTrace": [
        {          "moduleVersion": "11.0.7",
          "methodName": "dumpThreads0",
          "fileName": "ThreadImpl.java",
          "lineNumber": -2,
          "className": "sun.management.ThreadImpl",
          "nativeMethod": true
        },
        {
          "classLoaderName": null,
          "moduleName": "java.management",
          "moduleVersion": "11.0.7",
          "methodName": "dumpAllThreads",
          "fileName": "ThreadImpl.java",
          "lineNumber": 502,
          "className": "sun.management.ThreadImpl",
          "nativeMethod": false
        },
        {
          "classLoaderName": null,
          "moduleName": "java.management",
          "moduleVersion": "11.0.7",
          "methodName": "dumpAllThreads",
          "fileName": "ThreadImpl.java",
          "lineNumber": 490,
          "className": "sun.management.ThreadImpl",
          "nativeMethod": false
        },
        {
          "classLoaderName": null,
          "moduleName": null,
          "moduleVersion": null,
          "methodName": "threadMXBean",
          "fileName": "ThreadDumpResource.java",
          "lineNumber": 20,
          "className": "org.littlewings.resteasy.ThreadDumpResource",
          "nativeMethod": false
        },
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "invoke0",
          "fileName": "NativeMethodAccessorImpl.java",
          "lineNumber": -2,
          "className": "jdk.internal.reflect.NativeMethodAccessorImpl",
          "nativeMethod": true
        },
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "invoke",
          "fileName": "NativeMethodAccessorImpl.java",
          "lineNumber": 62,
          "className": "jdk.internal.reflect.NativeMethodAccessorImpl",
          "nativeMethod": false
        },
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "invoke",
          "fileName": "DelegatingMethodAccessorImpl.java",
          "lineNumber": 43,
          "className": "jdk.internal.reflect.DelegatingMethodAccessorImpl",
          "nativeMethod": false
        },
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "invoke",
          "fileName": "Method.java",
          "lineNumber": 566,
          "className": "java.lang.reflect.Method",
          "nativeMethod": false
        },

〜省略〜

次は、Threadクラスを使った方。

$ curl -s localhost:8080/thread/all-threads-stacktraces | jq

結果は、こんな感じ。

{
  "thread_dump": [
    {
      "threadName": "main",
      "threadId": 1,
      "blockedTime": -1,
      "blockedCount": 0,
      "waitedTime": -1,
      "waitedCount": 1,
      "lockName": "java.lang.Thread@411755fc",
      "lockOwnerId": -1,
      "lockOwnerName": null,
      "daemon": false,
      "inNative": false,
      "suspended": false,
      "threadState": "WAITING",
      "priority": 5,
      "stackTrace": [
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "wait",
          "fileName": "Object.java",
          "lineNumber": -2,
          "className": "java.lang.Object",
          "nativeMethod": true
        },
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "join",
          "fileName": "Thread.java",
          "lineNumber": 1305,
          "className": "java.lang.Thread",
          "nativeMethod": false
        },

〜省略〜

    {
      "threadName": "vert.x-eventloop-thread-13",
      "threadId": 30,
      "blockedTime": -1,
      "blockedCount": 15,
      "waitedTime": -1,
      "waitedCount": 0,
      "lockName": null,
      "lockOwnerId": -1,
      "lockOwnerName": null,
      "daemon": false,
      "inNative": false,
      "suspended": false,
      "threadState": "RUNNABLE",
      "priority": 5,
      "stackTrace": [
        {
          "classLoaderName": null,
          "moduleName": "java.management",
          "moduleVersion": "11.0.7",
          "methodName": "dumpThreads0",
          "fileName": "ThreadImpl.java",
          "lineNumber": -2,
          "className": "sun.management.ThreadImpl",
          "nativeMethod": true
        },
        {
          "classLoaderName": null,
          "moduleName": "java.management",
          "moduleVersion": "11.0.7",
          "methodName": "dumpAllThreads",
          "fileName": "ThreadImpl.java",
          "lineNumber": 502,
          "className": "sun.management.ThreadImpl",
          "nativeMethod": false
        },
        {
          "classLoaderName": null,
          "moduleName": "java.management",
          "moduleVersion": "11.0.7",
          "methodName": "dumpAllThreads",
          "fileName": "ThreadImpl.java",
          "lineNumber": 490,
          "className": "sun.management.ThreadImpl",
          "nativeMethod": false
        },
        {
          "classLoaderName": null,
          "moduleName": null,
          "moduleVersion": null,
          "methodName": "threadMXBean",
          "fileName": "ThreadDumpResource.java",
          "lineNumber": 20,
          "className": "org.littlewings.resteasy.ThreadDumpResource",
          "nativeMethod": false
        },
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "invoke0",
          "fileName": "NativeMethodAccessorImpl.java",
          "lineNumber": -2,
          "className": "jdk.internal.reflect.NativeMethodAccessorImpl",
          "nativeMethod": true
        },
        {
          "classLoaderName": null,
          "moduleName": "java.base",
          "moduleVersion": "11.0.7",
          "methodName": "invoke",
          "fileName": "NativeMethodAccessorImpl.java",
          "lineNumber": 62,
          "className": "jdk.internal.reflect.NativeMethodAccessorImpl",
          "nativeMethod": false
        },

〜省略〜

動作確認はできました、と。まあ、JSONだとスレッドダンプを見るにしてはちょっと辛いですが。

Spring Boot ActuatorもQuarkusも、頑張って自分でフォーマットしているので、マネするならこんな感じかなぁと。

https://github.com/spring-projects/spring-boot/blob/v2.3.0.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/PlainTextThreadDumpFormatter.java

https://github.com/quarkusio/quarkus/blob/1.5.0.Final/core/runtime/src/main/java/io/quarkus/runtime/graal/DiagnosticPrinter.java#L28-L73