Posted at

JVM言語とJDKの関係を理解する

More than 1 year has passed since last update.


背景

2017年の9月にOracleがJDKの新たなリリース・モデルを発表して以降、Javaユーザーを中心として、新たな短いリリースサイクルに追従できるのか、現在のOracle JDKに代わって配布されるOpenJDKにはLTS(長期サポート)が用意されるのかといったことを含めて不安に思っている方が多いようです。OpenJDKのバイナリをリリースしてサポートを提供しているのはOracle一社ではないですし、Java 11 LTSが提供される予定のAdoptOpenJDKに最悪逃げればいいかなと個人的には思っています(AdoptOpenJDKのサポートについて完全に理解しているわけではないので、これはあくまで印象です)。ただ、OpenJDK LTSが提供されればそれに越したことはないので、OpenJDK LTSが提供されることを願いたいです。

ただ、それは別にして、JVM言語が新しいJDKのリリース・モデルに追従できるのか、といった疑問がJVM言語やJavaユーザからよく聞こえてくるようになりました。そのような不安は私からするとJVMのアーキテクチャに関する理解が不足していることから来ているように思うので、この記事で、そういった不安を払拭できればと思っています。


JVM言語とは

JVM言語という言葉にちゃんとした定義はないですが、概ね、プログラムを最終的にJavaのクラスファイルに変換する処理系がデフォルトであるような言語を指すようです。比較的よく知られているものだけでも、

など様々なものがあります。中でも比較的使われているのが、Scala、Kotlinあたりでしょうか。それはともかく、これらのJVM言語全般について、新しいJDKのリリースモデルに移行することによって、処理系作成者のメンテナンスコストが増大し、追従できなくなるのではといった不安を持っている人が多いようです。Java Platformの上で動く言語と考えるとそういう不安は無理もないことと言えますが、実際のところは、そういった不安については杞憂に終わる可能性が非常に高いです。


JVMとクラスファイル

JVMは誕生してから今までずっとクラスファイルをその基本単位とするようなアーキテクチャでした。クラスファイルというと、.class形式のファイルが思い浮かぶ人が多いですが、より一般的にはメモリ上に生成されてロードされるクラスファイルも含みます。そのようなクラスファイルは、いわゆるバイトコード変換に基づいたフレームワークでしばしば生成されます。すごくおおざっぱに言えば、JVMというのはクラスファイルのロードやリンク、実行を行うような仮想マシンと言えます。

クラスファイルの詳しい構造についての説明は省きますが3、おおまかに言って、基本的な情報が格納されているコンスタントプール(constant pool)と、コンスタントプールに付加される属性(attribute)の2種類からなります。なお、いわゆるバイトコード(命令)は、属性です。そして、ここが重要なのですが、私の知る限り、JVM(JDK)は今まで、コンスタントプールや属性に格納される情報を追加することはあっても、削除することはありませんでした4。これは、Javaのエコシステムを考える上でとても重要です。現在、Javaのライブラリは概ね、Maven Centralにjar形式でパッケージングされて公開されていますが、もし、これらの情報について仕様から削除してしまえば、これまでのJavaライブラリのほとんどあるいは全てが新しいバージョンのJavaから使えなくなってしまいます。ですから、Oracleがそのようなことをする可能性はとても低いと見て良いでしょう。


今後のJDKの進化

今後のJDKは約半年ごとに新しいリリースが提供されますが、その中には属性の拡張やコンスタントプールの拡張を含むものがあります(ちなみに、属性はこれまでさんざん拡張されてきましたが、コンスタントプールはほとんど拡張されていません)。たとえば、JDK 11ではコンスタントプールの種類として CONSTANT_Dynamic が追加される予定です 5 ただし、この追加によって、既存のクラスファイルが読み込めなくなるということはないはずです。これは、バイトコード命令の追加でも同じです。Javaではクラスファイルの後方互換性を保ってきたし、これからも守られる可能性が高いことが重要です。


JVM言語によるクラスファイルの生成

JVM言語が新しいJavaのリリースモデルの影響をどのくらい受けるかを考える上では、それらの言語が、プログラムを処理して(どのように/どのような)クラスファイルを生成しているかが重要です。これは、ほぼ必ず静的にクラスファイルを生成するScalaやKotlinと、必要に応じて動的にクラスファイルにコンパイルするApache GroovyやClojureでは若干違います。

簡単に言えば、ScalaやKotlinの処理系がやっていることはほぼjavacと同じです。ScalaやKotlinのプログラムをクラスパスを指定して読み込んで、.classファイル(あるいはそのまとまりとしての.jarファイル)を生成します。コンパイル後の.classファイルは特殊なものでなく、ScalaやKotlinの標準ライブラリのランタイムをくっつければそのままJavaプログラムとして実行できます。そのため、新しいJDKでも、少なくともコンパイル後のバイナリについては、若干の例外を除き心配する必要がありません。一方、それらのコンパイラは新しいJDKで追加されたバイトコード命令やコンスタントプール、属性を適切に読み取れなければ正常に動作しない可能性があります。これは、主に処理系の中でクラスファイルを読む部分が担当すべきところですが、新しいバイトコード命令を適当に読み飛ばし、追加されたコンスタントプールや属性についてもとりあえず認識して読み飛ばせば最低限の対応としては十分だと思われます。大掛かりなコンスタントプールの追加やバイトコード命令の追加があった場合、対応がやや面倒になる可能性は否定できませんが、極端な話、それらの追加について、ScalaやKotlinプログラムから「見えない」ようにクラスファイル読み取り器に変更を加えればいいので、さほどコストが大きくなるとは思えません。

Apache GroovyやClojureではクラスファイルを生成するタイミングがずれるため、事情が多少異なりますが、根本的にはあまり変わらないはずです。結局、外のクラスファイルに関する名前解決ができればいいのですから。

というわけで、従来の動作を新しいJDKで維持するだけなら、必要なコストはそれほど大きくありません。ただし、Java 9のモジュール機能は、そもそもJavaでも、いくらかの非互換を伴う変更であり(クラスファイルフォーマットの変更とは別の理由です)、対応がやや面倒です。そのため、Scalaは最新バージョンでもモジュール機能を完全には利用できません(Java 10でScalaプログラムをコンパイルすることはできます)。


JVM言語による新しいJDKの機能の利用

今後のJDKのリリース予定を見ると、値型の追加や特殊化など、実行時の性能を向上させるための変更やそれにともなうクラスファイルへのコンスタントプールや属性の追加が予定されているようです。もし、各JVM言語がそういったJDKの改善のメリットを受けようとするなら、大規模な処理系の改修が必要になる可能性があります。Scalaでも、Java 8のラムダ(というよりinvokedynamic)やインタフェースがメソッド実装を持てるようになったことを活かすために、Scala 2.12では処理系に大きく手が入りました。KotlinについてはJava 6のクラスファイルを吐けるようにするために抜本的なフォーマットの変更は行われていないように見えます(最近のKotlin事情を追えていないため、何か間違いがあったらコメントをください)。ですが、現状のJVMの性能をJavaと同程度に引き出せるKotlinやScalaに関して、そういった改善に即座に追従する動機はないように思われます。


まとめ


  • Javaはこれまでクラスファイルフォーマットの非互換な変更を行っていない(コンスタントプールや属性の削除)


    • 今後、そのような変更が行われる可能性は非常に低い(既存のJavaライブラリが利用できなくなるため)



  • ScalaやKotlinが生成したクラスファイルは新しいJVMでも(ほぼ)そのまま実行できる

  • ScalaやKotlinのコンパイラ、Apache GroovyやClojureのように実行時にコンパイルを行うものについては、新しいJDK上で動かすために改修が必要になる可能性がある


    • ただし、既存のプログラムをそのままコンパイルできるようにするだけなら、必要な変更は多くない



  • 新しいJDKで改善された機能をJVM言語が利用したいなら、大規模な改修が必要になるかもしれない


    • ScalaやKotlinはJavaと同じくらいにはJVMを活用できるので、緊急性は低い

    • Clojure等ではそういった機能を利用したい要望がより強いかもしれない(未確認)



というわけで、言語の開発チームのリソースがよほどかつかつでない限り(というか細かい修正できないくらいかつかつだったら、その言語は既に結構まずいところに来ていると思いますが)、あまり心配する必要はない、というのが私の意見です。





  1. Jythonのページを見る限り、現在は開発は停滞しているようです 



  2. Nashornは既に非推奨になっているようですが 



  3. 詳細を知りたい方は https://docs.oracle.com/javase/specs/jvms/se10/html/index.html を読んでみてください 



  4. 厳密には、盲腸のようになった領域や命令があって、現在使われてなかったりするのですが、一応使えるはずです 



  5. 詳しくは http://openjdk.java.net/jeps/309 を参照