JVMはそんなに重くない

Clojureに反対する大きな理由がJVMです。この役立たずは重いですからね。

これは、数週間前にZA TechのSlackで見た投稿です。休暇中にClojureの話題を何件か見たのですが、投稿者はJVMについても繰り返し言及していました。

私はこの投稿についてSlack上で少しつぶやいていましたが、もっと広く理解され議論されるように、本稿を書くことを決めました。

背景

以前は、私もJVMは重いと思っていました。2000年代の初めにJVMとPHPと比べていた頃の話です。当時は、.NETやColdFusionなど、別の重い製品が他にもありました。また、PerlやPythonという軽めの製品もありましたが、私はWindowsを使っていたのでActivePerlやActivePythonはやはり少し重めでした。

私が初めてJVMに対する“恐れ”を克服したのは、小規模な製品アプリを、JRubyを使ってHerokuでデプロイした時です。この小さい製品は、1つのルーチンを行うためだけに作られました。大量のPDFを生成し、それを保存、共有するためにiSign(今は使われていません)にアップロードするのです。iSign自体は、昔ながらのRailsアプリで3つのAMI上にホストされていました。オリジナルのJVM(-server -Xmx=512Mを除く)を実行する小さなDynoはかなり速いスピードでPDFを生成するため、毎回実行するたびに3ノードのクラスタを停止させていました。

実際に使うにはまだ少し重いと思っていましたが、醜いアヒルの子のようで、私はとても気に入っていました。

その後もJRubyの開発や成功事例について多かれ少なかれ学んでいき、Rubyfuza 2015Charles Nutterの素晴らしいプレゼンを聞いて感動したので、自分の使命として、JRuby のテストしか行っていないRubyプロジェクトのためにプルリクエストを作成しました。これが、それっきりになっているのは、私の悪い所ですね。

時を進めて2016年へ

2016年の11月に、Railsアプリをゼロから作ろうと試みてみました。私のマシンでRubyのプログラミングを行ったのは数カ月ぶりです。実はこの時rbenv がbrew upgradeされたため、セットアップしていたRubyが全て消えていたのですが、私はそれに気付きもしませんでした。

これはwebsocketsについてJozi.rbでプレゼンしようとしていた時のことです。

はじめに、RailsでReact を扱う感覚を示すためにReact on Railsのリポジトリをいじることから始めました。私は数カ月間re-frameを使っていたので、生のReactも扱う自信があったのです。

これが見事に失敗しました。

一つのサンプルアプリをcloneしてきて動かすために、XCodeをアップグレードし、XCodeのコマンドラインツールをアップグレードし(合計で6GB以上)、Rubyの新しいバージョンとBundlerをインストールし、サンプルアプリ内でbundle installを実行し、というような手順を踏みます。シンプルですよね?大多数のRailsアプリと同様に今回のサンプルアプリも依存グラフのどこかでlibv8に依存していて、そのサイズは単独で1GB以上ありました。

全てが完了するまでに何時間もかかりました。

とても素晴らしいデモを実行している時に、ジャンケンゲームにHCMBが使われていることに気が付きました。私はEmber.jsを知っており時間もなかったので、代わりにEmber.jsを使ってフロントエンドを構築することにしました。

ここでも同じように、nvmをアップデートし、適切なバージョンのNode.jsをインストールし、Ember CLIをインストールし、アプリを生成し、npmとBower経由で依存関係をインストールし、、というような手順が必要です。

少し試しましたが、お手上げでした。代わりに、このサイトに訪れる多くの人にその経験を共有することにしたのです。これはとても恥ずかしいことでした。今までずっと属してきた世界が、全く知らない場所のように感じられました。

JVMは重いかと言う本題に戻りましょう。

どのように重さを量るか

  • ダウンロード時のJDKのサイズでしょうか?
  • 実行時に使用するリソースの量でしょうか?
  • ライブラリが消費するディスク領域の大きさでしょうか?
  • デプロイのための手順の数でしょうか?
  • 日々の作業であなたを手間取らせた時間でしょうか?

これらの質問は、JVMについて考える際に私たちに芽生える感情的な抵抗を拭い去るのに役立ちます。この抵抗や先入観というのは無視できない存在で、長い時間をかけて私たちを消耗させる要因となりえます。

それでは、以下の内容を確認し、問題に立ち向かってみましょう。

導入時のコストは本当に大きいのか

ここでいうJVMの“重い”というのは全くの思い込みです。まずは大きいと思われている、インストール時のコストについて考えてみましょう。200MB近くあるJDKのダウンロードサイズを、15MB以下しかないNode.jsやRubyと比べると重く感じるでしょう。しかし、これはベースラインに過ぎません。Node.jsとRubyの両方のケースにおいてシステム上にCコンパイラが必要であり、これだけで数百MBあります。更に残念なことに、Production環境にもコンパイラが必要になるでしょう。

Node.jsやRubyが必要とするものの膨張・肥大化は、このような小さくてインクリメンタルな手順によって隠蔽され表面的には分かりません。しかし、途中で立ち止まり、全てをよく調べてみれば、トータルでかかる時間は言うまでもありませんし、200MBでJDKをダウンロードする方が効率的だと分かるはずです。

なお、あなたのwebpackのconfigを読み解いていくといった踏み込んだ話をするつもりはさらさらありません。また新しいツールが登場したら乗り換えることになると思われますので。

https://twitter.com/iamdevloper/status/517616294909464576

JVMの実行はそんなに重いのか

JVMは高速です。おそらく世の中で最も速いランタイムのひとつでしょう。時間と共に速度が改善し筋肉質になっています。何千人もの優秀なエンジニアがJVMの改良に取り組んでおり、過去21年に渡って非常に多くのエンジニアがコントリビュートし続けてきました。

JVMには本物のスレッドがあり、複数コアをサポートしていて、カリカリにチューニングすることもできますしそのままにしておくこともできます。唯一知っておくべきことがあるとすれば、JVMのメモリの設定方法です。それが分かっていれば、あなたの環境の制約条件下で力を発揮してくれます。

Herokuへのデプロイですか? java -server -Xmx512m beast.jar.を実行してください。もしこれでは十分でないというなら、収入があるなら有料で誰かにアドバイスを求めましょうか…もしくは、StackOverflowで聞いてみてもいいでしょう。

これは、Charlesを始めとするJRubyコミュニティの人たちが推進し続けている重要なポイントです。あなたが何もしなくても、アプリはJVMがリリースされる度に確実に高速化します(JRubyの発展とは別に)。

ディスク使用量はそんなに大きいのか

私は気になって、~/.m2フォルダを見てみたところ、Clojure開発を行った9カ月間で蓄積されたのは、たった1010MBの依存関係モジュールでした。まだ1GBにすら届いていません。

$ du -sh /usr/local/opt/rbenv/versions/2.3.3 ~/.nvm/versions/node/v6.9.1 ~/.m2
690M    /usr/local/opt/rbenv/versions/2.3.3
232M    /Users/kenneth/.nvm/versions/node/v6.9.1
1010M   /Users/kenneth/.m2

Rubyを新規でインストールしましたが、このブログとMiddleman(私はMiddlemanに関するある修正を行っています)にはシステム要件があります。そうです、この静的なブログを実行しツールへのコントリビュートを行うために700MB近いストレージを必要としています。

Nodeの方はEmber.js、DocPadとBowerをインストールしただけで使用容量が200MBを超えています。

デプロイはそんなに重いのか

おそらく、これから私がどのような話をするか察しがついていることでしょう。

ビルドすると一つのJARファイルが生成されます。このファイルは別の場所でアプリを実行するのに必要な全ての情報を保持しています。単にJARファイルを必要な場所に保存するだけで、あとはJVMが好きにやってくれます。

JARファイルを使う場合、大規模なアプリケーションサーバにアプリをデプロイすることは必要でなく、高性能なHTTPサーバをJARファイルに簡単にバンドルすることができるのです。Node.jsの人もRubyの人もサーバにアプリをデプロイしているというのに、どういうわけでJARファイルは同じようにしないのでしょうか。私も以前はそう考えていました。

私は個人的に、本番環境でapt-get install build-essentialsを実行しなくて安心しています。

JVMとの日々

私は8GBメモリを搭載した2012年製のMacBook Proで、JVMのプロセスを少なくとも5つ実行しています。毎日、それも1日中です。絶対に5つのRailsアプリを同時に走らせようとはしませんでしたが。

5つとは何を指しているのかと言うと、2つはDatomic(transactorとconsole)、1つはバックエンドのAPIサーバ、そしてもう1つは私が取り組んでいるフロントエンドの何かです。バックグラウンドで自動テストを実行していたこともたまにありました。これらのJVMプロセスのかなりの部分において、メモリにロードされるバイト数が全く同じになるはずなので、MacOSのメモリ圧縮が間違いなく役に立っていると確信しています。 

Activity monitor main

Activity monitor java

私がこのようなことをすると10カ月前に予言されていたら、きっと笑い飛ばしていたでしょう。正気な人が5つ、またはそれ以上のJVMプロセスを実行させるでしょうか。賭けても良いですが、そう思うのは絶対に私だけではないはずです。

ちなみに、Classpathやその類の設定関係はどうでしょうか。Clojureが提供する素晴らしいツールのおかげで、それらには触れる必要性がありません。npmやBundlerを使うのも同じ目的だと思いますが、Include Pathについてはいじる必要がありません。いじってもいいですが、そうすると今は見えていない別の問題にぶつかる恐れがあります。

REPLの喜び

もしJVMのインスタンスの開始と停止を継続して行わなければならないとしたら、確実に気が変になるでしょう。これには随分昔JRubyを使っていた時に悩まされました。Clojureとその素晴らしいREPLのおかげで、よほど何かをやらかしてしまうというレアなケースが起きない限りは、ありがたいことにJVMのインスタンスを再起動せずに済みます。Figwheelを何日間も、問題なく実行できることはその証左と言えます。

結論

ターゲットとしてJVMを評価する前に、十分に注意して下さい。言語としてのJavaをあらゆる手段で評価したとしても、仮想マシンとは区別して考えましょう。

私はかつてあなたと同じことを信じていました。JVMを巨獣だと思っていたのです。今は、何千もの巨人にサポートしてもらいJVMに命令飛ばすことができて感謝しています。

決してこの記事を「Node.jsの終わり」や「Rubyの終わり」の兆しのように受け取らないでください。新しい視点で見てみましょう。もしJVMに切り替えることができないとしても、少なくともあなたの世界から膨張・肥大化したものを取り除くためにできることがないか考えてみましょう。

貴重な時間を割いて、この記事を読んでくださったことに感謝します。では、Clojureについて学びSimple Made Easyを体験してみましょうか。