見習いプログラミング日記

Javaを中心に色々なことを考えてみます。目指せ本物のプログラマ。

Quarkus入門

GlassFish Users Group Japan 勉強会 2019 SpringQuarkusについて紹介しました。話そうと思っていたけれどもスライドに入らなかったことをブログで補足してみます。

Quarkusとは何か

とにかく起動が高速な Java EE / MicroProfile ベースのAPIでコードが書けるフレームワークです。スライド中でも言及していますが、GraalVMのnative-imageコマンドによってネイティブコンパイルすると100ミリを切るスピードでJAX-RS/CDI/JPAで構成されるCRUDアプリケーションが起動します。

Javaは起動が遅い、Goのようなシングルバイナリによる起動の方が便利でJavaは手間が掛かると言われる課題を解決しています。

続きについてはスライドを参照してください。ここからは、スライドに入らなかった内容や、当日話そうと思っていたけど忘れていた内容をまとめます。

Quarkusがビルド時に自動生成するクラス

mvn clean packageすると、target/wiring-classesにQuarkusがビルド時にクラスファイルを展開してuber-jarの中に含めるような動きをします。JAX-RS/CDI/JPAのサンプルアプリケーションの場合、アプリケーションのパッケージsample.quarkus.jpajaxrscdijtaだけでなく、ランタイム自体のクラスファイルが生成されている様子がわかります。

$ tree target/wiring-classes
target/wiring-classes
├── META-INF
│   ├── build-config.properties
│   ├── quarkus-default-config.properties
│   └── services
│       ├── io.quarkus.arc.ComponentsProvider
│       └── io.quarkus.arc.ResourceReferenceProvider
├── io
│   └── quarkus
│       ├── agroal
│       │   └── runtime
│       │       ├── DataSourceProducer.class
│       │       ├── DataSourceProducer_Bean$$function$$6.class
│       │       ├── DataSourceProducer_Bean.class
│       │       ├── DataSourceProducer_ClientProxy.class
│       │       └── DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_Bean.class
│       ├── arc
│       │   ├── ActivateRequestContextInterceptor_Bean.class
│       │   ├── runtime
│       │   │   └── LifecycleEventRunner_Bean.class
│       │   ├── runtimebean
│       │   │   └── RuntimeBeanProducers.class
│       │   └── setup
│       │       └── Default_ComponentsProvider.class
│       ├── deployment
│       │   └── steps
│       │       ├── AgroalProcessor$build3.class
│       │       ├── AgroalProcessor$configureRuntimeProperties6.class
│       │       ├── ArcAnnotationProcessor$build10.class
│       │       ├── ConfigBuildStep$validateConfigProperties11.class
│       │       ├── HibernateOrmProcessor$build8.class
│       │       ├── HibernateOrmProcessor$build9.class
│       │       ├── HibernateOrmProcessor$startPersistenceUnits14.class
│       │       ├── LifecycleEventsBuildStep$startupEvent17.class
│       │       ├── LoggingResourceProcessor$setupLoggingRuntimeInit5.class
│       │       ├── LoggingResourceProcessor$setupLoggingStaticInit1.class
│       │       ├── NarayanaJtaProcessor$build4.class
│       │       ├── ResteasyScanningProcessor$setupInjection12.class
│       │       ├── RuntimeBeanProcessor$build2.class
│       │       ├── ThreadPoolSetup$createExecutor7.class
│       │       ├── UndertowArcIntegrationBuildStep$integrateRequestContext13.class
│       │       ├── UndertowBuildStep$boot16.class
│       │       └── UndertowBuildStep$build15.class
│       ├── hibernate
│       │   └── orm
│       │       ├── panache
│       │       │   └── PanacheEntity.class
│       │       └── runtime
│       │           ├── DefaultEntityManagerProducer_Bean.class
│       │           ├── DefaultEntityManagerProducer_ProducerField_entityManager_Bean.class
│       │           ├── JPAConfig_Bean.class
│       │           ├── RequestScopedEntityManagerHolder_Bean$$function$$7.class
│       │           ├── RequestScopedEntityManagerHolder_Bean.class
│       │           ├── RequestScopedEntityManagerHolder_ClientProxy.class
│       │           └── TransactionEntityManagers_Bean.class
│       ├── narayana
│       │   └── jta
│       │       └── runtime
│       │           ├── NarayanaJtaProducers_Bean.class
│       │           ├── NarayanaJtaProducers_ProducerMethod_transactionManager_9989455b3b53ac81c17ca945c636473b7202fe4e_Bean$$function$$8.class
│       │           ├── NarayanaJtaProducers_ProducerMethod_transactionManager_9989455b3b53ac81c17ca945c636473b7202fe4e_Bean.class
│       │           ├── NarayanaJtaProducers_ProducerMethod_transactionManager_9989455b3b53ac81c17ca945c636473b7202fe4e_ClientProxy.class
│       │           ├── NarayanaJtaProducers_ProducerMethod_transactionSynchronizationRegistry_ad29dd72d7aa0c9be8f98e90052c29fc262ea31a_Bean$$function$$9.class
│       │           ├── NarayanaJtaProducers_ProducerMethod_transactionSynchronizationRegistry_ad29dd72d7aa0c9be8f98e90052c29fc262ea31a_Bean.class
│       │           ├── NarayanaJtaProducers_ProducerMethod_transactionSynchronizationRegistry_ad29dd72d7aa0c9be8f98e90052c29fc262ea31a_ClientProxy.class
│       │           └── interceptor
│       │               ├── TransactionalInterceptorMandatory_Bean.class
│       │               ├── TransactionalInterceptorNever_Bean.class
│       │               ├── TransactionalInterceptorNotSupported_Bean.class
│       │               ├── TransactionalInterceptorRequired_Bean.class
│       │               ├── TransactionalInterceptorRequiresNew_Bean.class
│       │               └── TransactionalInterceptorSupports_Bean.class
│       ├── runner
│       │   ├── ApplicationImpl1.class
│       │   ├── AutoFeature.class
│       │   └── GeneratedMain.class
│       └── runtime
│           └── generated
│               ├── BuildTimeConfig.class
│               ├── BuildTimeConfigRoot.class
│               ├── RunTimeConfig.class
│               ├── RunTimeConfigRoot.class
│               └── RunTimeDefaultConfigSource.class
├── javax
│   ├── enterprise
│   │   ├── context
│   │   │   └── control
│   │   │       └── ActivateRequestContext_Shared_AnnotationLiteral.class
│   │   └── inject
│   │       └── Produces_Shared_AnnotationLiteral.class
│   ├── persistence
│   │   └── PersistenceContext_Shared_AnnotationLiteral.class
│   └── transaction
│       └── Transactional_Shared_AnnotationLiteral.class
└── sample
    └── quarkus
        └── jpajaxrscdijta
            ├── EmployeeResource_Bean.class
            ├── EmployeeService_Bean$$function$$1.class
            ├── EmployeeService_Bean.class
            ├── EmployeeService_ClientProxy.class
            ├── EmployeeService_Subclass$$function$$2.class
            ├── EmployeeService_Subclass$$function$$3.class
            ├── EmployeeService_Subclass$$function$$4.class
            ├── EmployeeService_Subclass$$function$$5.class
            └── EmployeeService_Subclass.class

スライド中で言及している以下のようなコードの@Injectの解決の場合、

package sample.quarkus. jpajaxrscdijta;

@Path("/")
public class EmployeeResource {

    @Inject
    private EmployeeService service;

wiring-classes/sample/quarkus/jpajaxrscdijta/EmployeeResource_Bean.classにインジェクション実装が含まれており、putfield命令でシンプルにフィールド名serviceに値を設定するクラスファイルが自動生成されている様子が確認できます。

javap -v wiring-classes/sample/quarkus/jpajaxrscdijta/EmployeeResource_Bean.class
...
  public sample.quarkus.jpajaxrscdijta.EmployeeResource create(javax.enterprise.context.spi.CreationalContext);
    descriptor: (Ljavax/enterprise/context/spi/CreationalContext;)Lsample/quarkus/jpajaxrscdijta/EmployeeResource;
    flags: ACC_PUBLIC
        ...
        30: aload_3
        31: checkcast     #57                 // class sample/quarkus/jpajaxrscdijta/EmployeeService
        34: putfield      #61                 // Field sample/quarkus/jpajaxrscdijta/EmployeeResource.service:Lsample/quarkus/jpajaxrscdijta/EmployeeService;

これらのクラスをデプロイしてから動的バイトコード生成で行うのではなく、事前に可能な処理はビルド時に実行するため、GraalVMによってネイティブバイナリを生成しなくても、java -jarでも従来のThorntail/WildFlyと比較して高速に起動しています。

Quarkusでスレッドダンプ

GraalVM Community Editionのnative-imageコマンドで生成された実行バイナリでは、kill -3 (SIGQUIT) が投げられるとHotSpotJVMと異なりプロセスが終了します。

Quarkusではシグナルハンドラを独自に実装しており、kill -3を実行プロセスに投げるとコンソールにスレッドダンプを出力します。内部的にはsun.misc.SignalHandlerを使って実装しています。

2019-05-21T14:22:58.915Z
Thread dump follows:

"XNIO-1 Accept" #22 prio=5 tid=0x7f6cb77e9700
   java.lang.thread.State: RUNNABLE
	at com.oracle.svm.core.posix.headers.linux.LinuxEPoll.epoll_wait(LinuxEPoll.java)
	at sun.nio.ch.EPollArrayWrapper.epollWait(EPollArrayWrapper.java:326)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:101)
	at org.xnio.nio.WorkerThread.run(WorkerThread.java:532)
	at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:473)
	at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193)
...

ヒープダンプはGraalVM Enterprise EditionによりGraalVMレイヤで実現されています。無償で利用可能なGraalVM Community Editionでは現状ヒープダンプを取得することはできません。

まとめ

QuarkusはMicroProfileベースのAPIでコードが書ける、高速起動のフレームワークです。JBoss系コミュニティ由来との製品としては、Java EEからJakarta EEへの移管に伴い標準仕様の観点では変化がゆっくりになっていましたが、MicroProfile仕様の一部をWildFlyで実装したり、Quarkusのような新しい試みが行われています。ぜひ試してみるのはいかがでしょうか。