ラムダ式をより深く理解したいと考える読者にとって、これは気になるテーマの1つだろう。その秘密をお教えしよう。8で導入されたラムダ式は、Javaの言語仕様の歴史において最大級の変更だとされている。これはあくまでもJava言語の文法レベルの話であり、Javaプログラムを実行するJava仮想マシン(JVM)に新たな仕組みが導入されたわけではない。言語仕様に関する旧バージョンとの相違はコンパイラとランタイムが吸収し、バイトコードレベルでの互換性は保たれているということだ。それでは、このような大きな変更を、既存のJVMの仕組みの中でどのように実現したのだろうか。それを知ることは、ラムダ式をより深く理解する手助けとなるかもしれない。エンジニアリング)による解説をお届けする。Head」の内容を基に構成しています。初めに、本記事の狙いを明確にしておきたい。ここでは、読者が次の2点についての知識を得ることを目的とする。ラムダ式に該当するバイトコードを理解することラムダ式のパフォーマンスの影響を把握することこれらの目的を達成するためには、その前提として次の知識が必要になる。292については、一般の開発者はなじみが薄いかもしれない。292、つまりラムダ式の内部的な実現方式について説明する。Project」と呼ばれるプロジェクトとして発足したものだ。同プロジェクトの目的は、JVM上でJava以外の言語のランタイムをより簡単に実装できるようにすることにある。その背景には、JRubyやGroovy、Jythonなど、JVM上で動作する軽量言語が多数登場してきたことがあった。バック氏は、「JVM上にJava以外の言語のランタイムを実装する際、特にネックとなったのが、動的な型付けのメソッド呼び出しをサポートしていないことでした」と振り返る。従来のJVMには、次の4種類のメソッド呼び出しの命令が用意されていた。その他のメソッド(コンストラクタ、スーパークラス、privateメソッドなど)「これら4種類の命令は、Java言語に限って言えば、極めて適切なものです。他の言語にとっては必ずしも十分ではない場合がありました。292では、その"十分ではない場合"を改善することを目指したのです」(バック氏)これらの命令は、ターゲットのメソッドを特定して呼び出すことが前提となっている。Java言語は静的型付け言語であり、コンパイルの段階でターゲットを明示して呼び出しを行うバイトコードを生成する。したがって、上記4種類の命令ですべての要件を満たすことができる。動的型付け言語では、ターゲットの特定をプログラム実行時まで遅らせる必要が生じる。既存のJVMにはそのような命令が存在しないため、各ランタイム側でメソッド・ディスパッチをエミュレートして動的メソッド呼び出しを仮想的に実現する必要があった。この方法はエミュレーション処理のオーバーヘッドが高いことに加えて、JITコンパイラによる最適化が働かず、プログラムのパフォーマンスが悪いという問題を抱えていた。292では当初、Java言語以外のメソッド呼び出しのロジックも直接サポートするようにJVMを拡張する予定であった。ディスパッチのロジックが言語ごとに異なるため、単にサポートするロジックを増やすだけのアプローチでは対応できなかったという。そこで採用された解決策が、「JVM側ではディスパッチのロジックを固定せず、代わりにディスパッチのロジックを定義するAPIを提供する」というものであった。292で追加されたのが、次の2つの機能である。APIで定義したロジックを使ってディスパッチを行う命令(JVM側の修正)invokedynamicバイトコード命令は、通称「indy(インディ)」と呼ばれる。このindy命令の追加は、「JVMの歴史上、非常に衝撃的な出来事」(バック氏)であった。「Java言語には、この命令に該当する機能がありません。そのため当初、この命令がJava言語で使われる予定はありませんでした。そのことを踏まえると、この仕様変更は2つの点で歴史的な出来事だったと言えます。1つは、JVMが誕生して以来、初めて新しい命令が追加されたこと。APIの次の3つの要素が重要となる。クラスMethodHandleクラスCallSitebootstrapメソッドこのうち、MethodHandleはメソッドを指定するためのクラスであり、型記述子とは無関係にメソッドを指定できる仕組みを提供する。「これは関数ポインタのような働きをするクラスだと考えればご理解いただきやすいでしょう」とバック氏は話す。APIとして具現化したものであり、indy命令が実行されるとCallSiteのインスタンスが作られる。CallSiteはMethodHandleを保持しており、ディスパッチされたメソッドを特定することができる。bootstrapメソッドはindy命令の1回目の実行時に呼び出されるメソッドであり、呼び出し元のindy命令にひも付いたCallSiteオブジェクトを生成して返す。CallSiteオブジェクトを生成する際にはMethodHandleの初期値を設定する。以上を踏まえ、1つのindy命令のライフサイクルをまとめると、次のようになる。【1回目の実行】(1)特定のindy命令が初めて実行される(2)bootstrapメソッドが呼び出され、(1)のindy命令に該当するメソッドを選ぶ(3)bootstrapメソッドが(1)のindy命令に該当するCallSiteオブジェクトを生成して返す(4)CallSiteのMethodHandleが指定するメソッドへジャンプする【2回目以降の実行】(1)CallSiteのMethodHandleが指定するメソッドへジャンプするこのライフサイクルからもわかるように、bootstrapメソッドが呼ばれるのは初回のみであり、2回目以降の呼び出しは初回よりも速く処理される。※クリックすると拡大画像が見られますまた、この仕組みのもう1つの特筆すべき点として、「CallSiteが持つMethodHandleは、言語のランタイム側から自由に変更できる」ということが挙げられる。引数のオブジェクトの型が異なる場合や、メソッドが動的に変更された場合などには、CallSiteを修正してMethodHandleが指示するメソッドを変えればよい。「メソッドへのリンク処理をランタイム側に任せてしまおう」という発想である。お試しいただけるトライアル・サービスをご提供しております。permission.×このサイトでは、利用状況の把握や広告配信などのために、Cookieなどを使用してアクセスデータを取得・利用しています。これ以降ページを遷移した場合、Cookieなどの設定や使用に同意したことになります。Cookieなどの設定や使用の詳細、オプトアウトについては詳細をご覧ください。