Posted at

Javaの組み込みWebサーバ3つ、Tomcat、Jetty、Undertowのうち、GraalVMで動作したのは?

GraalVMをご存知ですか?Javaをネイティブコンパイルし、ケースによってはメモリ消費を1/16に押さえてくれるツールです。

ただし、GraalVMがコンパイルできるアプリケーションには制約があり、すべてのJavaアプリケーションがネイティブコンパイルできるわけではありません。

今回はネイティブなWebアプリケーションを作成するために、Webサーバを組み込んだJavaアプリケーションがGraalVMでコンパイル、実行できるかを検証してみました。


施行

gradle shadowを使ってFat Jarを作成、GraalVMのnative-imageコマンドにて-jarオプションでFat Jarを指定してコンパイルします。


環境

$ native-image --version

GraalVM Version 1.0.0-rc13
$ java -version
openjdk version "1.8.0_202"
OpenJDK Runtime Environment (build 1.8.0_202-20190206132754.buildslave.jdk8u-src-tar--b08)
OpenJDK GraalVM CE 1.0.0-rc13 (build 25.202-b08-jvmci-0.55, mixed mode)


Tomcat(tomcat-embed-core v9.0.17)

だめでした。

ソース


ソース抜粋

public static void main(String[] args) throws Exception {

Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.getConnector();
File base = new File("src/main/static/");
Context context = tomcat.addContext("/app", base.getAbsolutePath());
Tomcat.addServlet(context, "default", new DefaultServlet()).addMapping("/");

Tomcat.addServlet(context, "hello", new HttpServlet() {
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Writer w = resp.getWriter();
w.write("Hello, World!");
w.flush();
}
}).addMapping("/hello");
tomcat.start();
tomcat.getServer().await();
}


コンパイルに失敗。

$ native-image  -jar build/libs/app-all.jar

Build on Server(pid: 2099, port: 50704)
[app-all:2099] classlist: 1,259.46 ms
[app-all:2099] (cap): 1,512.61 ms
[app-all:2099] setup: 7,054.04 ms
[app-all:2099] analysis: 10,298.20 ms
Error: unsupported features in 2 methods
Detailed message:
Error: Class initialization failed: com.sun.naming.internal.ResourceManager$AppletParameter
Original exception that caused the problem: java.lang.ExceptionInInitializerError
...(省略)

残念。


Jetty(jetty-server v9.4.15.v20190215)

行けました!


ソース抜粋

public static void main(String[] args) throws Exception {

Server server = new Server(8080);

server.setHandler(new AbstractHandler() {

@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter out = response.getWriter();
out.println("Hello World!");
baseRequest.setHandled(true);
}
});

server.start();
server.join();
}


コンパイル & 実行

$ native-image -jar build/libs/app-all.jar

Build on Server(pid: 2523, port: 51183)*
[app-all:2523] classlist: 3,325.81 ms
[app-all:2523] (cap): 1,309.95 ms
[app-all:2523] setup: 8,400.08 ms
2019-04-04 22:16:54.214:INFO::ForkJoinPool-2-worker-3: Logging initialized @22713ms to org.eclipse.jetty.util.log.StdErrLog
[app-all:2523] (typeflow): 11,805.43 ms
[app-all:2523] (objects): 9,466.98 ms
[app-all:2523] (features): 354.09 ms
[app-all:2523] analysis: 37,080.95 ms
[app-all:2523] universe: 650.05 ms
[app-all:2523] (parse): 3,276.85 ms
[app-all:2523] (inline): 6,226.93 ms
[app-all:2523] (compile): 31,668.53 ms
[app-all:2523] compile: 57,307.38 ms
[app-all:2523] image: 2,385.43 ms
[app-all:2523] write: 859.86 ms
[app-all:2523] [total]: 145,307.12 ms
$ ./app-all
2019-04-04 22:19:55.501:INFO:oejs.Server:main: jetty-9.4.z-SNAPSHOT; built: 2019-02-15T16:53:49.381Z; git: eb70b240169fcf1abbd86af36482d1c49826fa0b; jvm 1.8.0_202
2019-04-04 22:19:55.502:INFO:oejs.AbstractConnector:main: Started ServerConnector@6b64b5db{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2019-04-04 22:19:55.502:INFO:oejs.Server:main: Started @2ms

無事起動!ブラウザでアクセスもできました!

すばらしい。


Undertow(undertow-core v2.0.19.Final)

だめでした。。。


ソース抜粋

public static void helloWorldHandler(HttpServerExchange exchange) {

exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, "text/plain");
exchange.getResponseSender().send("Hello World!");
}

public static void main(String[] args) {
Undertow server = Undertow.builder().addHttpListener(8080, "0.0.0.0", App::helloWorldHandler).build();
server.start();
}


コンパイル。

$ native-image -jar build/libs/app-all.jar

Build on Server(pid: 2523, port: 51183)
[app-all:2523] classlist: 2,361.03 ms
[app-all:2523] (cap): 1,720.81 ms
[app-all:2523] setup: 7,458.13 ms
[app-all:2523] analysis: 6,485.28 ms
Error: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved type during parsing: org.osgi.framework.FrameworkUtil. To diagnose the issue you can use the --allow-incomplete-classpath option. The missing type is then reported at run time when it is accessed the first time.

その後、--allow-incomplete-classpathオプションを付けたりなんだりしたのですが、どうしてもコンパイルできませんでした。残念。


というわけで正解はJettyでした。

一つでもコンパイルできたものがあったということで、成果ありです。1/16のメモリリソースでのWebアプリ運用の光が見えてきました!

あとは、JPAが動けば、簡単なWebアプリケーションは作れそうですね。QuarkusでJPAが動いているようなので、コンパイルできることを期待しています。