Java以外の言語に対するJava仮想マシンのサポート

次の項目について説明します。

はじめに

Java SEプラットフォームは、次の特徴を持つアプリケーションの開発を可能にします。

Java SEプラットフォームは次の領域およびその他の領域に対する堅牢なサポートも提供します。

OracleのHotSpot JVMは次のツールおよび機能も提供します。

Java以外の言語は、Java SE 7プラットフォームを介してJVMのインフラストラクチャを利用することで、パフォーマンスを潜在的に最適化できます。主要なメカニズムはinvokedynamic命令で、動的型付け言語のコンパイラおよびランタイム・システムのJVMへの実装を簡略化します。

静的型付けと動的型付け

プログラミング言語は、コンパイル時に型チェックを実行する場合、静的に型付けされます。型チェックとは、プログラムが型安全であることを確認する処理のことです。プログラムのすべてのオペレーションの引数が正しい型であれば、そのプログラムは型安全です。

Javaは静的型付け言語です。クラス変数とインスタンス変数、メソッド・パラメータ、戻り値、およびその他の変数のすべての型付け情報は、プログラムがコンパイルされるときに利用されます。Javaプログラミング言語のコンパイラはこの型情報を使用して強く型付けされたバイト・コードを生成することで、JVMでの実行時に効率的な実行が可能になります。

次のHello Worldプログラムの例は、静的型付けを示しています。型は太字で示しています。

import java.util.Date;

public class HelloWorld {
    public static void main(String[] argv) {
        String hello = "Hello ";
        Date currDate = new Date();
        for (String a : argv) {
            System.out.println(hello + a);
            System.out.println("Today's date is: " + currDate);
        }
    }
}

プログラミング言語は、実行時に型チェックを実行する場合、動的に型付けされます。JavaScriptとRubyは動的型付け言語の例です。これらの言語は、アプリケーション内の値が予期する型に一致することを、コンパイル時ではなく実行時に確認します。これらの言語は通常、コンパイル時に使用できる型情報を持ちません。オブジェクトの型は実行時にのみ判別できます。そのため以前は、JVM上でこれらを効率的に実装するのは困難でした。

次は、Rubyプログラミング言語で記述されたHello Worldプログラムの例です。

#!/usr/bin/env ruby
require 'date'

hello = "Hello "
currDate = DateTime.now
ARGV.each do|a|
  puts hello + a
  puts "Date and time: " + currDate.to_s
end

すべての名前が型宣言なしで導入されています。また、メイン・プログラムはホルダー型(JavaクラスHelloWorld)の中にはありません。RubyでJava forループに相当するものは、変数ARGVの動的な型の中にあります。ループの本体は、動的な言語に共通する特徴である、クロージャと呼ばれるブロックに含まれます。

静的型付け言語は強く型付けされた言語とはかぎらない

強い型付けを特徴とするプログラミング言語は、そのオペレーションに提供される値の型に制限を指定します。強い型付けを実装するコンピュータ言語は、引数が間違った型を持っている場合、オペレーションの実行を妨げます。逆に、弱い型付けを特徴とする言語は、オペレーションの引数が間違った型または互換性のない型を持つ場合、これらの引数を暗黙的に変換(キャスト)します。

静的型付けプログラミング言語は、強い型付けまたは弱い型付けを使用できます。同様に、動的型付け言語も、強い型付けまたは弱い型付けを適用できます。たとえば、プログラミング言語Rubyは動的に型付けされ、かつ強く型付けされます。変数がある型の値で初期化されると、プログラミング言語Rubyは変数を別のデータ型に暗黙的に変換しません。プログラミング言語Rubyは次を許可しません。

a = "40"
b = a + 2

この例では、プログラミング言語RubyはFixnum型を持つ数字2を文字列に暗黙的にキャストしません。

動的型付け言語のコンパイルの課題

2つの数字(どの数値型でも構いません)を追加してその合計を返す、次の動的型付けメソッドaddtwoについて考えます。

def addtwo(a, b)
       a + b;
end

あなたの組織が、メソッドaddtwoが記述されたプログラミング言語用のコンパイラとランタイム・システムを実装しているとします。静的型付けか動的型付けかにかかわらず、強く型付けされた言語では、+ (加算演算子)の動作はオペランドの型によって決まります。静的型付け言語のコンパイラは、aおよびbの静的型に基づいて、+のどの実装が適切かを選択します。たとえば、aおよびbの型がintの場合、JavaコンパイラはJVMのiadd命令で+を実装します。JVMのiadd命令は静的に認識されるオペランド型を必要とするため、この加算演算子はメソッド呼出しにコンパイルされます。

これに対し、動的型付け言語のコンパイラは実行時まで選択を保留する必要があります。文a+bはメソッド呼び出し+(a, b)としてコンパイルされます(+はメソッド名)。(JVMでは+という名前のメソッドが許可されますが、Javaプログラミング言語では許可されません。)この動的型付け言語用のランタイム・システムが、aおよびbが整数型の変数であることを識別できるとします。ランタイム・システムは、任意のオブジェクト型ではなく整数型に専用化された+の実装を呼び出すことを選択します。

動的型付け言語のコンパイルの課題は、プログラムがコンパイルされた後に、メソッドまたは関数の最適な実装を選択できるランタイム・システムをどのように実装するかです。すべての変数をObject型のオブジェクトとして扱うと効率的に機能しません。Objectクラスに+という名前のメソッドが含まれていないためです。

Java SE 7は、ランタイム・システムがコール・サイトとメソッド実装との間のリンケージをカスタマイズできる、invokedynamic命令を導入しています。この例では、invokedynamicコール・サイトは+です。invokedynamicコール・サイトは、ブートストラップ・メソッド (サイトをリンクするためにJVMによって1回呼び出される、動的型付け言語のコンパイラによって指定されるメソッド)によって、メソッドにリンクされます。+を呼び出すinvokedynamic命令をコンパイラが発行したと想定し、かつランタイム・システムがメソッドadder(Integer,Integer)を認識すると想定したうえで、ランタイムは次のようにinvokedynamicコール・サイトをadderメソッドにリンクできます。

IntegerOps.java

class IntegerOps {

  public static Integer adder(Integer x, Integer y) {
    return x + y;
  }
}

Example.java

import java.util.*;
import java.lang.invoke.*;
import static java.lang.invoke.MethodType.*;
import static java.lang.invoke.MethodHandles.*;

class Example {

  public static CallSite mybsm(
    MethodHandles.Lookup callerClass, String dynMethodName, MethodType dynMethodType)
    throws Throwable {

    MethodHandle mh =
      callerClass.findStatic(
        Example.class,
        "IntegerOps.adder",
        MethodType.methodType(Integer.class, Integer.class, Integer.class));

    if (!dynMethodType.equals(mh.type())) {
      mh = mh.asType(dynMethodType);
    }

    return new ConstantCallSite(mh);
  }
}

この例では、IntegerOpsクラスは動的言語のランタイム・システムに付属のライブラリに属します。

メソッドExample.mybsmは、invokedynamicコール・サイトをadderメソッドにリンクするブートストラップ・メソッドです。

オブジェクトcallerClassはルックアップ・オブジェクト(メソッド・ハンドルを作成するためのファクトリ)です。

メソッドMethodHandles.Lookup.findStatic (callerClassルックアップ・オブジェクトから呼び出される)は、メソッドadderのstaticメソッド・ハンドルを作成します。

: このブートストラップ・メソッドはadderメソッドで定義されたコードにのみinvokedynamicコール・サイトをリンクし、invokedynamicコール・サイトに指定される引数はIntegerオブジェクトであると想定します。ブートストラップ・メソッドは、ブートストラップ・メソッドのパラメータ(この例ではcallerClassdynMethodName、およびdynMethodType)が変化すると実行されるコードにinvokedynamicコール・サイトを正しくリンクするために、追加コードを必要とします。

クラスjava.lang.invoke.MethodHandlesおよびjava.lang.invoke.MethodHandleには、既存のメソッド・ハンドルに基づいてメソッド・ハンドルを作成するさまざまなメソッドが含まれています。この例では、メソッド・ハンドルmhのメソッド・タイプがパラメータdynMethodTypeで指定されたメソッド・タイプと一致しない場合に、メソッドasTypeを呼び出します。これにより、ブートストラップ・メソッドはinvokedynamicコール・サイトをメソッド・タイプが厳密に一致しないJavaメソッドにリンクできます。

ブートストラップ・メソッドによって返されるConstantCallSiteインスタンスは、個々のinvokedynamic命令に関連付けられているコール・サイトを表します。ConstantCallSiteインスタンスのターゲットは永続的で変更できません。この場合、Javaメソッドはadder 1つのみ存在します(コール・サイト実行の候補)。このメソッドはJavaメソッドでなくてもかまいません。代わりに、ランタイム・システムで使用できるこのようなメソッドがいくつかあり、それぞれが異なる引数型を処理する場合、ブートストラップ・メソッドmybsmは、dynMethodType引数に基づいて正しいメソッドを動的に選択できます。

invokedynamic命令

invokedynamic命令は、動的言語のコンパイラおよびランタイム・システムのJVM上への実装を簡略化し、潜在的に改善します。invokedynamic命令は、言語実装者がカスタム・リンケージ動作を定義することを許可することで、これを行います。この点は、invokevirtualなどその他のJVM命令とは異なります(Javaクラスおよびインタフェースに固有のリンケージ動作がJVMによって固定されている)。

invokedynamic命令の各インスタンスは動的コール・サイトと呼ばれます。動的コール・サイトは、最初はリンクされていない状態(呼び出すコール・サイトにメソッドが指定されていない)です。前述のように、動的コール・サイトはブートストラップ・メソッドによってメソッドにリンクされます。動的コール・サイトのブートストラップ・メソッドは、動的型付け言語のコンパイラによって指定されるメソッドで、サイトをリンクするためにJVMによって1回呼び出されます。ブートストラップ・メソッドから返されるオブジェクトは、コール・サイトの動作を永続的に決定します。

invokedynamic命令には、定数プール・インデックス(その他のinvoke命令と同じ形式)が含まれます。定数プール・インデックスはCONSTANT_InvokeDynamicエントリを参照します。このエントリは、ブートストラップ・メソッド(CONSTANT_MethodHandleエントリ)、動的にリンクされるメソッドの名前、および動的にリンクされるメソッドへの呼出しの引数の型および戻り型を指定します。

次は、invokedynamic命令の例です。この例では、ランタイム・システムがブートストラップ・メソッドExample.mybsmを使用することで、このinvokedynamic命令(+、加算演算子)で指定された動的コール・サイトをIntegerOps.adderメソッドにリンクします。メソッドadderおよびmybsmは、「動的型付け言語のコンパイルの課題」セクションで定義されています(わかりやすくするために改行が追加されています)。

invokedynamic   InvokeDynamic
  REF_invokeStatic:
    Example.mybsm:
      "(Ljava/lang/invoke/MethodHandles/Lookup;
        Ljava/lang/String;
        Ljava/lang/invoke/MethodType;)
      Ljava/lang/invoke/CallSite;":
    +:
      "(Ljava/lang/Integer;
        Ljava/lang/Integer;)
      Ljava/lang/Integer;";

: これらのセクションのバイト・コード例は、ASM Javaバイト・コード操作および分析フレームワークの構文を使用しています。

動的にリンクされるメソッドをinvokedynamic命令で呼び出すには、次の手順が必要です。

  1. ブートストラップ・メソッドを定義する
  2. 定数プール・エントリを指定する
  3. invokedynamic命令を使用する

1. ブートストラップ・メソッドを定義する

実行時に、JVMがはじめてinvokedynamicを検出すると、ブートストラップ・メソッドが呼び出されます。このメソッドは、invokedynamic命令によって指定された名前を、実行されるべきコード(ターゲット・メソッド、メソッド・ハンドルによって参照される)にリンクします。JVMが同じinvokedynamic命令を再度実行した場合、ブートストラップ・メソッドを呼び出さず、リンクされたメソッド・ハンドルを自動的に呼び出します。

ブートストラップ・メソッドの戻り型はjava.lang.invoke.CallSiteである必要があります。CallSiteオブジェクトは、invokedynamic命令とそれがリンクされるメソッド・ハンドルの、リンク状態を表します。

ブートストラップ・メソッドは3つ以上のパラメータを取ります。

  1. MethodHandles.Lookupオブジェクト、invokedynamic命令のコンテキスト内でメソッド・ハンドルを作成するファクトリ。
  2. Stringオブジェクト、動的コール・サイト内で言及されるメソッド名。
  3. MethodTypeオブジェクト、動的コール・サイトの解決済み型シグニチャ。
  4. 必要に応じて、invokedynamic命令への1つ以上の追加静的引数。定数プールから取り出されるこれらの引数の目的は、言語実装者がブートストラップ・メソッドに便利な追加メタデータを安全かつコンパクトにエンコードするのを支援することです。各コール・サイトには独自のブートストラップ・メソッドが指定される可能性があるため、原則として名前と追加引数は冗長になります。ただし、そのような運用ではおそらく、大きなクラス・ファイルや定数プールが生成されます。

ブートストラップ・メソッドの例については、「動的型付け言語のコンパイルの課題」セクションを参照してください。

2. 定数プール・エントリを指定する

前述のように、invokedynamic命令にはタグCONSTANT_InvokeDynamicを持つ定数プール内のエントリへの参照が含まれます。このエントリには、定数プール内のその他のエントリへの参照および属性への参照が含まれます。このセクションでは、invokedynamic命令で使用される定数プール・エントリについて簡単に説明します。詳細は、java.lang.invokeパッケージのドキュメントおよび「Java仮想マシン仕様」を参照してください。

定数プールの例

次は、メソッド+をJavaメソッドadderにリンクするブートストラップ・メソッドExample.mybsmを含む、クラスExampleの定数プールからの引用です。

    class #159; // #47
    Utf8 "adder"; // #83
    Utf8 "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;"; // #84
    Utf8 "mybsm"; // #87
    Utf8 "(Ljava/lang/invoke/MethodHandles/Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)
      java/lang/invoke/CallSite;"; // #88
    Utf8 "Example"; // #159
    Utf8 "+"; // #166

    // ...

    NameAndType #83 #84; // #228
    Method #47 #228; // #229
    MethodHandle 6b #229; // #230
    NameAndType #87 #88; // #231
    Method #47 #231; // #232
    MethodHandle 6b #232; // #233
    NameAndType #166 #84; // #234
    Utf8 "BootstrapMethods"; // #235
    InvokeDynamic 0s #234; // #236

この例のinvokedynamic命令の定数プール・エントリには、3つの値が含まれています。

0は、BootstrapMethods属性に格納されている指定子配列内の最初のブートストラップ・メソッド指定子を参照します。ブートストラップ・メソッド指定子は、定数プール表内にはありません。この独立した指定子配列に含まれています。各ブートストラップ・メソッド指定子にはCONSTANT_MethodHandle定数プール・エントリ(ブートストラップ・メソッドそのもの)へのインデックスが含まれています。

次は、BootstrapMethods属性を示す、同じ定数プールからの引用で、ブートストラップ・メソッド指定子の配列が含まれています。

  [3] { // Attributes

    // ...

    Attr(#235, 6) { // BootstrapMethods at 0x0F63
      [1] { // bootstrap_methods
        {  //  bootstrap_method
          #233; // bootstrap_method_ref
          [0] { // bootstrap_arguments
          }  //  bootstrap_arguments
        }  //  bootstrap_method
      }
    } // end BootstrapMethods
  } // Attributes

ブートストラップ・メソッドmybsmのメソッド・ハンドルの定数プール・エントリには3つの値が含まれています。

6はサブタグREF_invokeStaticです。このサブタグの詳細については、次のセクション「3. invokedynamic命令を使用する」を参照してください。

3. invokedynamic命令を使用する

次のバイト・コードはinvokedynamic命令を使用して、ブートストラップ・メソッドmybsm (動的コール・サイト(+、加算演算子)をメソッドadderにリンク)を呼び出します。この例では、+メソッドを使用して数字402を追加しています(わかりやすくするために改行が挿入されています)。

bipush  40;
invokestatic    Method java/lang/Integer.valueOf:"(I)Ljava/lang/Integer;";
iconst_2;
invokestatic    Method java/lang/Integer.valueOf:"(I)Ljava/lang/Integer;";
invokedynamic   InvokeDynamic
  REF_invokeStatic:
    Example.mybsm:
      "(Ljava/lang/invoke/MethodHandles/Lookup;
        Ljava/lang/String;
        Ljava/lang/invoke/MethodType;)
      Ljava/lang/invoke/CallSite;":
    +:
      "(Ljava/lang/Integer;
        Ljava/lang/Integer;)
      Ljava/lang/Integer;";

最初の4つの命令は、整数402をスタック上に置き、それらをjava.lang.Integerラッパー型にAutoboxingします。5つ目の命令が動的メソッドを呼び出します。この命令は、CONSTANT_InvokeDynamicタグを持つ定数プール・エントリを参照します。

REF_invokeStatic:
  Example.mybsm:
    "(Ljava/lang/invoke/MethodHandles/Lookup;
      Ljava/lang/String;
      Ljava/lang/invoke/MethodType;)
    Ljava/lang/invoke/CallSite;":
  +:
    "(Ljava/lang/Integer;
      Ljava/lang/Integer;)
    Ljava/lang/Integer;";

このエントリではCONSTANT_InvokeDynamicタグに4つのバイトが続きます。

この例では、動的コール・サイトにはAutoboxingされた整数値(最終ターゲットであるadderメソッドの型に厳密に一致)が渡されます。実際には、引数型と戻り型が厳密に一致する必要はありません。たとえば、invokedynamic命令はJVMスタックにそのオペランドの一方または両方をプリミティブ型int値として渡すことができます。オペランドの一方または両方が型指定のないObject値でもかまいません。invokedynamic命令は、その結果をプリミティブ型int値または型指定のないObject値として受け取ることもできます。どの場合も、mybsmdynMethodType引数がinvokedynamic命令によって要求されるメソッド型を正確に記述します。

adderメソッドに渡されるのは、プリミティブ型でも、型指定のない引数でも、戻り値でもかまいません。ブートストラップ・メソッドが、dynMethodTypeadderメソッドの型の違いを解決します。コードに示すように、これはターゲット・メソッドでasTypeを呼び出すことで簡単に実行されます。

リソース


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.