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, 2013, Oracle and/or its affiliates. All rights reserved.