6 非Java言語のサポート

この章では、Java仮想マシンの非Java言語機能について説明します。

非Java言語機能の概要

Java Platform, Standard Edition (Java SE)は、次の特徴を持つアプリケーションの開発を可能にします。

  • 一度書けば、どこでも実行できる
  • Javaサンドボックス・セキュリティ・モデルによって安全に実行できる
  • パッケージ化と配信が容易

Java SEプラットフォームは次の領域に対して堅牢なサポートを提供します。

  • 同時実行性
  • ガベージ・コレクション
  • クラスおよびオブジェクトへのリフレクション・ベースのアクセス
  • JVM Tool Interface (JVM TI): ツールで使用するためのネイティブ・プログラミング・インタフェース。JVMで実行されるアプリケーションの状態を検査する方法と、その実行を制御する方法の両方を提供します。

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

  • DTrace: アプリケーションおよびオペレーティング・システムの動作をモニターする動的トレース・ユーティリティ。
  • パフォーマンスの最適化
  • PrintAssembly: バイト・コード化されたメソッドおよびネイティブ・メソッド用のアセンブリ・コードを出力するJava HotSpotオプション。

Java SE 7プラットフォームは、Java以外の言語で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プログラミング言語はFixnum型を持つ数字2を文字列に暗黙的にキャストしません。


a = "40"
b = a + 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オブジェクトはlookupオブジェクト(メソッド・ハンドルを作成するためのファクトリ)です。

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

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

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

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

invokedynamic命令

JVMでの動的型付け言語のために、invokedynamic命令をコンパイラおよびランタイム・システムの実装で使用できます。invokedynamic命令を使用すると、言語実装者はカスタム・リンケージを定義できます。この点は、invokevirtualなどその他のJVM命令とは異なります(Javaクラスおよびインタフェースに固有のリンケージ動作がJVMによって固定されている)。

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

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

次の例では、ランタイム・システムが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命令を使用する

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

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

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

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

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

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

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

invokedynamic命令には、CONSTANT_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命令の定数プール・エントリには、次の値が含まれています。

  • CONSTANT_InvokeDynamicタグ
  • 符号なしshort値0
  • 定数プール・インデックス#234

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のメソッド・ハンドルの定数プール・エントリには次の値が含まれています。

  • CONSTANT_MethodHandleタグ
  • 符号なしbyte値6
  • 定数プール・インデックス#232

6,はREF_invokeStaticサブタグです。このサブタグの詳細は、invokedynamic命令を使用するを参照してください。

invokedynamic命令を使用する

次の例は、バイト・コードがinvokedynamic命令を使用して、動的コール・サイト(+、加算演算子)をadderメソッドにリンクするmybsmブートストラップ・メソッドを呼び出す方法を示しています。この例では、+メソッドを使用して数字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つのバイトが続きます。

  • 最初の2つのバイトが、ブートストラップ・メソッド指定子を参照するCONSTANT_MethodHandleエントリへの参照を構成しています。

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

    ブートストラップ・メソッド指定子へのこの参照は定数プール表に含まれていません。これはBootstrapMethodsという名前のクラス・ファイル属性によって定義された個別の配列に含まれています。ブートストラップ・メソッド指定子にはCONSTANT_MethodHandle定数プール・エントリ(ブートストラップ・メソッドそのもの)へのインデックスが含まれています。

    このCONSTANT_MethodHandle定数プール・エントリには3つのバイトが続きます。

    • 最初のバイトはREF_invokeStaticサブタグです。これは、このブートストラップ・メソッドがstaticメソッドのメソッド・ハンドルを作成することを意味します。このブートストラップ・メソッドが動的コール・サイトをstatic Java adderメソッドにリンクしていることに注意してください。

    • 次の2つのバイトは、メソッド・ハンドル作成対象のメソッドを表す、CONSTANT_Methodrefエントリを構成しています。

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

      この例では、ブートストラップ・メソッドの完全修飾名はExample.mybsmです。引数型はMethodHandles.LookupStringおよびMethodTypeです。戻り型はCallSiteです。

  • 次の2つのバイトがCONSTANT_NameAndTypeエントリへの参照を構成しています。

    
    +:
      "(Ljava/lang/Integer;
        Ljava/lang/Integer;)
      Ljava/lang/Integer;"
    

    この定数プール・エントリは、メソッド名(+)、引数型(2つのIntegerインスタンス)、および動的コール・サイトの戻り型(Integer)を指定しています。

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

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