Java8におけるindyとLambdaの絶妙な関係、もしくはSAMタイプを継承する内部クラスの.classファイルはどこへ行ったの?

JJUG CCC 2013 Springのさくらばさんのセッションで「Java 8の怖い話」として、Lambda式をJVM上で実行するにあたり、

  • invokedynamicを使っている
  • Lambda式の仕様は、意味的にはSamタイプを継承する内部クラスのインスタンスのはずだが、lambda式を使っているJavaソースをコンパイルしても、内部クラスに対応する.classファイルが生成されない

という話を聞きました。Lambdaの実行に、内部的にバイトコードを生成して使っている、という話だったのですが、indy使ってるってのは、良く考えると解せないです。Lambdaになんらかの動的な側面があるんでしょうかね???

ということで、調べてみました。

[:contents]

結論

結論から書くと、OracleのJava8のJDKのJavacは、Lambda式の本体を、SAMタイプを継承する内部クラスのメソッドではなく、コンパイル対象のクラスに直接所属するメソッドとしてコンパイルします。また、Lambda式の使用は、invokedynamic命令にコンパイルされます。そして確かに、コンパイル時には、そのメソッドを持つSAMタイプを継承するクラスの.classファイルは生成されません。

そして、invokedynamic命令の初回実行時に、その命令に付随させたブートストラップメソッドの処理の中で、SAMタイプを継承するクラスを動的にオンメモリにバイト列として生成し、それをクラスローダにロードさせ、そのクラスのインスタンスをnewする処理が、invokedynamicの実際の処理に置き換わります*1。この生成されたクラスのSAMメソッドではおそらく、MethodHandle経由で先の(2013/6/13削除。こちらを見ると、MH経由ではない)Lambda本体に相当するメソッドを呼び出します。

ちなみに、「オンメモリにクラスを動的に生成する」という処理を、Java VMではObjectweb asmライブラリを使って行なっています。JVMの中でasmを使ってるとは知らなかった。

indyをLambda式の評価に使ってる理由の一つは、おそらく、lambda式の初回呼び出し時までSAMタイプを継承したクラスの生成およびロードのタイミングを遅延させるためです。この方式なら、例えLambda式が何100個記述されていようとも、そのLambda式が実際に呼び出されなければクラスは全く生成もロードもされないので、クラスロード処理やクラスファイルを読み込むためのファイルアクセス処理が節約でき、起動や初期化の時間が短縮できるのでしょう。また、内部クラスという別クラスではなく、実際にそのクラスにLambda本体のメソッドがあるので、内部クラスを使った場合に生じていた可視性の問題の回避も簡略化できたはずです。

賢いわー。

(2013/06/12 23:52追記)
桜庭さんより(!)Facebookにてコメント頂き、本件に関し、この資料「Lambda: A peek under the hood」が参考になるとのこと。おお!! ざっと見ですが、利点として、字面が同じlambdaは生成SAMクラスを共有できる、とかもあるようです。Type profile pollutionは意味がわかりませんでしたorz。ありがとうございました>桜庭さん

確認してみよう

例えば、Lambda式を含む、

public class Test {
	static void foo(Runnable r) {
		r.run();
	}
	public static void main(String[] args) {
		foo(()->{System.out.println("hello");});
	}
}

こんなJavaソースをまずはコンパイルして実行してみます。

$ javac Test.java
$ java Test
hello
$ ls -la
total 16
drwxr-xr-x   4 uehaj  staff   136  6 12 22:22 ./
drwxr-xr-x  41 uehaj  staff  1394  6 12 22:20 ../
-rw-r--r--   1 uehaj  staff  1095  6 12 22:22 Test.class
-rw-r--r--   1 uehaj  staff   182  6 12 22:21 Test.java

確かに、Test.class以外のクラスファイルは生成されてませんね。javapしてみましょう。

$ javap -p Test
Compiled from "Test.java"
public class Test {
  public Test();
  static void foo(java.lang.Runnable);
  public static void main(java.lang.String[]);
  private static void lambda$0();   // ←★
}

★のところの「lambda$0();」がLamda式のボディに対応するメソッドです。

メソッドの宣言だけではなく、メソッド本体のバイトコードとかコンスタントプール情報も見るために、javapに-c -p -vオプションをつけて実行してみます。以降、行番号は判りやすさのために付与しました。

$ javap -p -c -v Test
     1	Classfile /Users/uehaj/work/201305/java8_2/y/Test.class
     2	  Last modified 2013/06/12; size 1095 bytes
     3	  MD5 checksum d69a213c626bb412c306b8f3ea88cac4
     4	  Compiled from "Test.java"
     5	public class Test
     6	  SourceFile: "Test.java"
     7	  InnerClasses:
     8	       public static final #56= #55 of #59; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
     9	  BootstrapMethods:
    10	    0: #25 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    11	      Method arguments:
    12	        #26 invokeinterface java/lang/Runnable.run:()V
    13	        #27 invokestatic Test.lambda$0:()V
    14	        #28 ()V
    15	  minor version: 0
    16	  major version: 52
    17	  flags: ACC_PUBLIC, ACC_SUPER
    18	Constant pool:
    19	   #1 = Methodref          #9.#21         //  java/lang/Object."<init>":()V
    20	   #2 = InterfaceMethodref #22.#23        //  java/lang/Runnable.run:()V
    21	   #3 = InvokeDynamic      #0:#29         //  #0:lambda$:()Ljava/lang/Runnable;
    22	   #4 = Methodref          #8.#30         //  Test.foo:(Ljava/lang/Runnable;)V
    23	   #5 = Fieldref           #31.#32        //  java/lang/System.out:Ljava/io/PrintStream;
    24	   #6 = String             #33            //  hello
    25	   #7 = Methodref          #34.#35        //  java/io/PrintStream.println:(Ljava/lang/String;)V
    26	   #8 = Class              #36            //  Test
    27	   #9 = Class              #37            //  java/lang/Object
    28	  #10 = Utf8               <init>
    29	  #11 = Utf8               ()V
    30	  #12 = Utf8               Code
    31	  #13 = Utf8               LineNumberTable
    32	  #14 = Utf8               foo
    33	  #15 = Utf8               (Ljava/lang/Runnable;)V
    34	  #16 = Utf8               main
    35	  #17 = Utf8               ([Ljava/lang/String;)V
    36	  #18 = Utf8               lambda$0
    37	  #19 = Utf8               SourceFile
    38	  #20 = Utf8               Test.java
    39	  #21 = NameAndType        #10:#11        //  "<init>":()V
    40	  #22 = Class              #38            //  java/lang/Runnable
    41	  #23 = NameAndType        #39:#11        //  run:()V
    42	  #24 = Utf8               BootstrapMethods
    43	  #25 = MethodHandle       #6:#40         //  invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    44	  #26 = MethodHandle       #9:#2          //  invokeinterface java/lang/Runnable.run:()V
    45	  #27 = MethodHandle       #6:#41         //  invokestatic Test.lambda$0:()V
    46	  #28 = MethodType         #11            //  ()V
    47	  #29 = NameAndType        #42:#43        //  lambda$:()Ljava/lang/Runnable;
    48	  #30 = NameAndType        #14:#15        //  foo:(Ljava/lang/Runnable;)V
    49	  #31 = Class              #44            //  java/lang/System
    50	  #32 = NameAndType        #45:#46        //  out:Ljava/io/PrintStream;
    51	  #33 = Utf8               hello
    52	  #34 = Class              #47            //  java/io/PrintStream
    53	  #35 = NameAndType        #48:#49        //  println:(Ljava/lang/String;)V
    54	  #36 = Utf8               Test
    55	  #37 = Utf8               java/lang/Object
    56	  #38 = Utf8               java/lang/Runnable
    57	  #39 = Utf8               run
    58	  #40 = Methodref          #50.#51        //  java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    59	  #41 = Methodref          #8.#52         //  Test.lambda$0:()V
    60	  #42 = Utf8               lambda$
    61	  #43 = Utf8               ()Ljava/lang/Runnable;
    62	  #44 = Utf8               java/lang/System
    63	  #45 = Utf8               out
    64	  #46 = Utf8               Ljava/io/PrintStream;
    65	  #47 = Utf8               java/io/PrintStream
    66	  #48 = Utf8               println
    67	  #49 = Utf8               (Ljava/lang/String;)V
    68	  #50 = Class              #53            //  java/lang/invoke/LambdaMetafactory
    69	  #51 = NameAndType        #54:#58        //  metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    70	  #52 = NameAndType        #18:#11        //  lambda$0:()V
    71	  #53 = Utf8               java/lang/invoke/LambdaMetafactory
    72	  #54 = Utf8               metaFactory
    73	  #55 = Class              #60            //  java/lang/invoke/MethodHandles$Lookup
    74	  #56 = Utf8               Lookup
    75	  #57 = Utf8               InnerClasses
    76	  #58 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    77	  #59 = Class              #61            //  java/lang/invoke/MethodHandles
    78	  #60 = Utf8               java/lang/invoke/MethodHandles$Lookup
    79	  #61 = Utf8               java/lang/invoke/MethodHandles
    80	{
    81	  public Test();
    82	    flags: ACC_PUBLIC
    83	    Code:
    84	      stack=1, locals=1, args_size=1
    85	         0: aload_0       
    86	         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
    87	         4: return        
    88	      LineNumberTable:
    89	        line 3: 0
    90	
    91	  static void foo(java.lang.Runnable);
    92	    flags: ACC_STATIC
    93	    Code:
    94	      stack=1, locals=1, args_size=1
    95	         0: aload_0       
    96	         1: invokeinterface #2,  1            // InterfaceMethod java/lang/Runnable.run:()V
    97	         6: return        
    98	      LineNumberTable:
    99	        line 5: 0
   100	        line 6: 6
   101	
   102	  public static void main(java.lang.String[]);
   103	    flags: ACC_PUBLIC, ACC_STATIC
   104	    Code:
   105	      stack=1, locals=1, args_size=1
   106	         0: invokedynamic #3,  0              // InvokeDynamic #0:lambda$:()Ljava/lang/Runnable;
   107	         5: invokestatic  #4                  // Method foo:(Ljava/lang/Runnable;)V
   108	         8: return        
   109	      LineNumberTable:
   110	        line 8: 5
   111	        line 9: 8
   112	
   113	  private static void lambda$0();
   114	    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
   115	    Code:
   116	      stack=2, locals=0, args_size=0
   117	         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   118	         3: ldc           #6                  // String hello
   119	         5: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   120	         8: return        
   121	      LineNumberTable:
   122	        line 8: 0
   123	}

長いっ!

なので以降、分解して見ていきます。

Lambda式はどうなったか

Lambdaの本体に対応しそうなのは、

   113	  private static void lambda$0();
   114	    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
   115	    Code:
   116	      stack=2, locals=0, args_size=0
   117	         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   118	         3: ldc           #6                  // String hello
   119	         5: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   120	         8: return        
   121	      LineNumberTable:
   122	        line 8: 0
   123	}

の部分であり、確かにlambda$0メソッドが、

()->{ System.out.println("hello"); }

の「System.out.println("hello")」近辺に対応していそうです。

invokedynamicの動き

まず、mainメソッドに対応する以下の部分を見ると、

   102	  public static void main(java.lang.String[]);
   103	    flags: ACC_PUBLIC, ACC_STATIC
   104	    Code:
   105	      stack=1, locals=1, args_size=1
   106	         0: invokedynamic #3,  0              // InvokeDynamic #0:lambda$:()Ljava/lang/Runnable;
   107	         5: invokestatic  #4                  // Method foo:(Ljava/lang/Runnable;)V
   108	         8: return        
   109	      LineNumberTable:
   110	        line 8: 5
   111	        line 9: 8

確かにinvokedynamic命令が生成されていることがわかります。このinvokedynamic命令に対応する「ブートストラップメソッド」は、javap出力の冒頭にある

     9	  BootstrapMethods:
    10	    0: #25 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    11	      Method arguments:
    12	        #26 invokeinterface java/lang/Runnable.run:()V
    13	        #27 invokestatic Test.lambda$0:()V
    14	        #28 ()V

ここのところですね。ブートストラップメソッドが表している実体は、静的メソッドへのポインタであり、ここでは具体的には、JDKの提供するAPIであるメソッドjava.lang.invoke.LambdaMetafactory.metaFactory()です。

LambdaMetaFactory

ブートストラップメソッドとして仕掛けられているLambdaMetaFactoryは、このケースでは最終的にjava.lang.invoke.InnerClassLambdaMetafactory.javaのspinInnerClass()あたりの以下の処理を呼び出します。

(略)
import jdk.internal.org.objectweb.asm.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;

(略)
    private final ClassWriter cw;                    // ASM class writer
(略)

    private Class<?> spinInnerClass() throws LambdaConversionException {
(略)    
        cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
                 lambdaClassName, null,
                 NAME_MAGIC_ACCESSOR_IMPL, interfaces);

        // Generate final fields to be filled in by constructor
        for (int i = 0; i < argTypes.length; i++) {
            FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argTypes[i].getDescriptor(),
                                            null, null);
            fv.visitEnd();
        }

        generateConstructor();

        MethodAnalyzer ma = new MethodAnalyzer();

        // Forward the SAM method
        if (ma.getSamMethod() == null) {
            throw new LambdaConversionException(String.format("Functional interface method not found: %s", samMethodType));
        } else {
            generateForwardingMethod(ma.getSamMethod(), false);
        }

        // Forward the bridges
        // @@@ The commented-out code is temporary, pending the VM's ability to bridge all methods on request
        // @@@ Once the VM can do fail-over, uncomment the !ma.wasDefaultMethodFound() test, and emit the appropriate
        // @@@ classfile attribute to request custom bridging.  See 8002092.
        if (!ma.getMethodsToBridge().isEmpty() /* && !ma.conflictFoundBetweenDefaultAndBridge() */ ) {
            for (Method m : ma.getMethodsToBridge()) {
                generateForwardingMethod(m, true);
            }
        }

        if (isSerializable) {
            generateWriteReplace();
        }

        cw.visitEnd();

        // Define the generated class in this VM.

        final byte[] classBytes = cw.toByteArray();

        ClassLoader loader = targetClass.getClassLoader(); // ★★
(略)
        return (Class<?>) Unsafe.getUnsafe().defineClass(lambdaClassName, classBytes, 0, classBytes.length,
                                                                   loader, pd);
    }

asmのClassWriterで生成したバイト列classBytesを、★★以降でクラスローダでロードしているようです。
どういうクラスを生成しているか、までは実は追ってないのですが、MethodHandle経由で渡してきたlambda$0をinvokeするようなコードを含むメソッドを含むクラスが生成されるのではないかと思います(もしくはlambda$0のバイトコード本体をコピーする??まさかね)。(2013/6/13削除)
こちらを見ると、直接lambda$0メソッドをinvokevirtual/invokestaticで呼んでいるようです(2013/6/13追記)。

まとめ

APIリファレンスが日本語に訳されたので、invokedynamicあたりのAPI(MethodHandle, CallSite,....)を初めて読む気になりました(へたれ)。ありがとうオラクルの人(寺田さん?)。

invokedynamicは今さらながらおもしろいな。今、Brainfuckindyで実装してみるテスト中です。

*1:この置き換えはinvokedynamicの通常動作です。CallSiteのインストールの話ね。

内部から見たVert.xとNode.jsとの比較

socket.ioがJavaGrailsから扱えるかを調べている関係でvert.xを調べていて興味深かったので、こちらにあるVert.xの記事を翻訳してみました。JGGUG G*Workshopにおける杉浦さんのVert.x資料もお奨めです。


Vert.xは急速に発達つつあるサーバ・フレームワークです。

世にあまたあるサーバ・フレームワークのいずれもが、多様なプロトコルをサポートし、高速であることが特長であると主張していますが、Vert.xはそれらよりも一歩抜きん出ています。例えば、Vert.xは、サーバサイドのネットワーク環境の確立と操作も対象としています。言いかえれば、Vert.xは、単一サーバ上のデーモン実行だけでなく、クラスタリング環境での複数サーバデーモンの実行を考慮しているのです。

したがって、Vert.xを調査するにあたっては、どのように高性能を実現しているかだけではなく、それが考慮しているネットワーク環境がどのようなものであるかを見ていくことが重要です。Vert.xの構造を調べることには、時間をかけるだけの価値があると思います。

Vert.xの哲学

Vert.xはNode.jsから影響を受けているプロジェクトです。Node.jsと同様に、Vert.xはイベントベースのプログラミング・モデルを提供するサーバ・フレームワークです。したがって、Vert.x APIは、Node.jsのそれと非常に似ています。なぜならこれらのモデルは両方とも非同期APIを提供するからです。

Node.jsはJavaScriptで開発されているのに対し、Vert.xはJavaで開発されています。しかし、単に「Node.jsのJavaバージョン」としてVert.xを理解するのは単純すぎる見方です。Vert.xはNode.jsの影響を受けていますが、Node.jsとは確かに異なるユニークな哲学を持っています。

Vert.xの設計哲学のコアは以下のように要約できます:

  • Polyglot…多言語対応
    Vert.xはJavaで作られていますが、Vert.xを使用するのにJavaを使う必要はありません。JavaやGroovyのようなJVMオペレーションに基づいた言語は当然ですが、Ruby, Python, JavaScriptなども使用することができます。JavaScriptを使用してサーバアプリケーションを構築する場合はNode.jsが競合になります。また、ScalaClojureのサポートも計画されています。
  • スーパーシンプルな並行実行モデル
    Vert.xを使ってサーバアプリケーションを構築する場合、単一スレッド・アプリケーションとしてコードを書くことができます。この事が意味するのは、同期やロック、volatile指定を使わなくてもマルチスレッド・プログラミングと同等の結果を得られるということです。
    Node.jsでは、JavaScript実行エンジンはマルチスレッドをサポートしないため、CPUコアをすべて利用するためには、複数の同じJavaScriptプログラムを実行しなければなりませんでした。しかしながら、Vert.xでは、1つのプロセス上でもCPUコア数に基づいた複数のスレッドを作成することができます。Vert.xでは、開発者がビジネス・ロジックの実装に集中できるようにマルチスレッディングを扱います。
  • イベント・バスの提供
    導入部で述べた様に、Vert.xの目標は単に「1つのサーバ処理デーモン」を開発することではありません。Vert.xは、相互に通信しあって動作する、Vert.xで構築された多様なサーバプログラム群を開発することを目標とします。このためVert.xはイベント・バスを提供し、ポイント・ツー・ポイント、あるいはPub/SubのようなMQ機能を使うことができます(イベント・バス機能を提供するために、Vert.xはインメモリデータ・グリッドであるHazelcastmを使用します)。このイベント・バスを使い、異なる言語で構築されたサーバアプリケーション同士が容易に相互通信することができます。
  • モジュール・システムと公式モジュール・リポジトリ
    Vert.xはモジュール・システムを持っています。このモジュール・システムは一種のコンポーネントと思えば良いでしょう。つまり、Vert.xで構築したサーバアプリケーション・プロジェクトは、それ自身がモジュールになります。モジュールシステムの目的は再利用です。モジュールは公式モジュール・リポジトリに登録することができ、他者と共有することができます。

NettyとVert.xの関係、および違いは?

Vert.xの性能について議論する前に、NettyとVert.xの関係をまとめておくと、それは「Vert.xはNettyを使用する」ということです。言いかえれば、Vert.xのIOはすべてNettyによって処理されます。したがって、Vert.xとNettyのパフォーマンスの差異を検証することに意味はありません。

Vert.xは、Nettyとは異なる独立したAPIおよび機能を提供するサーバ・フレームワークであり、またNettyとは異なる目的で設計されています。Nettyは低レベルIOを処理するフレームワークであり、Vert.xはNettyよりも高いレベルのIOを処理します。

Node.jsとのパフォーマンスの比較

Vert.xによって提供される機能はNode.jsのものとは異なっているとしても、パフォーマンスの比較は重要です。以下の図1および図2は、Vert.x(JavaRuby、Groovy)とNode.jsの性能を示しています(ソースは: http://vertxproject.wordpress.com/2012/05/09/vert-x-vs-node-js-simple-http-benchmarks/)

図1は、HTTPサーバを構築し、200/OKレスポンスだけを返す場合のパフォーマンスの比較で、図2は、72バイトの静的なHTMLファイルがレスポンスとして返される場合のパフォーマンスの比較を示しています。

図1: 200/OKレスポンスだけが返された場合のパフォーマンスの比較

図2:72バイトのスタティックなHTMLファイルが返される場合のパフォーマンスの比較

上記のパフォーマンス比較はVert.x開発者によって提示されたものであり、テストは厳密な環境の下では行なわれていません。相対的な差異だけを見てください。

一点、注目すべきポイントとしては、Vert.xでのJavaScriptの性能がNode.jsよりも良いということです。とはいえ、この性能比較が信頼できるとしても、このことだけをもってして「Vert.xがNode.jsより良い」と言うのは難しいでしょう。Node.jsはSocket.ioのような素晴しいモデルを提供しており、参考資料も充実しているからです。

Vert.x用語集

Vert.xは独自の用語を定義したり、いくつかの一般的な用語をVert.x用に再定義しています。したがって、Vert.xを理解するためには、Vert.xで定義された用語を理解することが必要です。下記はVert.xを使う上で頻繁に使う用語です:

Verticle
Javaではmainメソッドを持ったクラスです。Verticleは、mainからさらに参照されるスクリプトを含んでいるかもしれません。さらにjarファイルやリソースを含んでいるかもしれません。アプリケーションは1つもしくはイベントバスで通信し合う複数のVerticleから構成されます。Javaに関しては、独立して実行可能なクラスあるいはJavaファイルとして理解することができます。
Vert.xインスタンス
VerticleはVert.xインスタンス内で実行されます。また、Vert.xインスタンスJVMインスタンス中で実行されます。したがって、単一のVert.xインスタンス中で多くのVerticlesを同時に実行することができます。Verticleは、それぞれ、自分自身のユニークなクラス・ローダーを持つことができます。こうすることで、staticメンバーおよびグローバル変数を通じたVerticles間の直接アクセスを防ぐことができます。多くのVerticleは、ネットワーク上の複数のホスト中で同時に実行することができます。また、Vert.xインスタンスはイベント・バスによってクラスタ化することができます。
並行処理
Verticleインスタンスは、常に同一のスレッド上で実行されることが保証されます。すべてのコードはシングルスレッド操作として開発することができ、Vert.xでの開発は容易になります。加えて、競合条件やデッドロックを防止します。
イベントベースのプログラミング・モデル
Node.jsフレームワークのように、Vert.xはイベントベースのプログラミング・モデルを提供します。Vert.xを使用してサーバをプログラムする場合、開発のためのほとんどのコードはイベントハンドラと関係があります。例えば、イベントハンドラを、TCPソケットからデータを受け取るように準備すると、それはデータ受信時に呼び出されます。さらに、イベントハンドラは「イベント・バスがメッセージを受け取った」「HTTPメッセージを受信した」「接続が切られた」「タイマーがタイムアウトした」などの通知を受け取ることができます。
イベントループ
Vert.xインスタンスは内部でスレッド・プールを管理しています。Vert.xは、スレッド・プールの数をCPUコア数になるべくぴったりと一致させます。スレッドはそれぞれイベント・ループを実行します。イベントループの実行中には、イベントの有無を確認します。例えば、ソケット中に読み取るべきデータがあるかどうか、イベントが発生したタイマーはどれか、などを確認します。イベントループ中で処理すべきイベントがある場合、Vert.xは対応するハンドラを呼びます(もちろんハンドラの処理期間が長すぎたりブロッキングI/Oがあったりした場合、追加処理が必要になります)。
メッセージパッシング
Verticlesはイベント・バスを通信に使用します。Verticleをアクターと仮定すると、メッセージパッシングはErlangで採用されていることで有名なアクターモデルに似ています。Vert.xサーバ・インスタンスは多くのVerticleインスタンスを持っており、インスタンス間でメッセージパッシングを行うことができます。したがって、システムはマルチスレッド配下でVerticleコードを実行することなく、使用可能なコア数を拡張することができます。
共有データ
メッセージパッシングは非常に有用です。しかしながら、それは必ずしもすべてのタイプのアプリケーション並列状況に対して最適なアプローチだとは限りません。キャッシュは、最もポピュラーなそういう例の1つです。1つのVerticleだけがキャッシュを保持しているのは非能率的です。他のVerticle群がキャッシュを必要とする場合、Verticleはそれぞれ同じキャッシュ・データを管理するべきです。したがって、Vert.xはグローバルなアクセス方法である共有マップを提供します。Verticlesは不変のデータだけを共有します。
Vert.xコア
その名の通り、これはVert.xの中核機能です。Verticleが直接呼ぶことができる関数はコアに含まれています。Vert.xがサポートする各プログラミング言語APIは、コアにアクセスすることができます。
Vert.xアーキテクチャ
Vert.xの単純なアーキテクチャーは次の図3に示されます。

図3:Vert.xアーキテクチャー(出所: http://www.javacodegeeks.com/2012/07/osgi-case-study-modular-vertx.html )

Vert.xのデフォルト実行ユニットはVerticleです。また、いくつかのVerticleは、同時に1つのVert.xインスタンス上で実行することができます。Verticleはイベントループ・スレッド上で実行されます。Vert.xインスタンス群はネットワーク上の1つのホストだけでなく、複数のホスト上でも実行することができます。この際、Verticlesやモジュールはイベントバスで通信します。
まとめると、vert.xアプリケーションはVerticle群とモジュールで構成されており、それらの通信はイベント・バスで行います。

vert.xプロジェクト構造
下記は、Vert.x Githubページからコピーしたソース・コードのVert.xプロジェクト構造をExlipseで表示しているところです。


図4:Vert.xソースのツリー。


全体の構成は以下のとおりです:

  • vertx-coreはコアライブラリーです。
  • vertx-platformは分散処理とライフ・サイクルを管理します。
  • vert-langコアJava APIを言語に提供するときに使われます。

Gradleがそのプロジェクトビルドシステムとして採用されています。antとMavenよりも優れた点があります。

Vert.xのインストールおよび単純な例の実行

Vert.xは、Java 7のinvokeDynamicを使用するため、JDK7が必須です。Vert.xのインストールは容易です。

  1. http://vertx.io/downloads.htmlから任意のフォルダに圧縮したインストール・ファイルをダウンロードしてください。
  2. ファイルを解凍し、binディレクトリをPATH環境変数に加えてください。

以上がすべてです(訳注: gvm Toolを使ってもvert.xをインストール可能です)。コマンド・ウィンドウ中で、vertx versionを実行してください。バージョン情報が成功裡に表示されれば、インストールは完了です。

例1

さて、JavaScriptで「Hello World!」を表示する単純なWebサーバーを構築し実行しましょう。
次のコードをserver.jsというファイルに保存してください。
これは、Node.jsのコードとほとんど同一です。

  load('vertx.js');

  vertx.createHttpServer().requestHandler(function(req) {
  req.response.end("Hello World!");}) .listen(8080, 'localhost');

vertxコマンドを使用して、作成したserver.jsアプリケーションを実行します:

  $ vertx run server.js

ブラウザでhttp://localhost:8080を開き、「Hello World!」が表示されれば成功です。

例2

別の例を、別の言語を使ってやってみましょう。次のコードはJavaで書かれています。静的ファイルの内容を読みとって、HTTPレスポンスとして返すWebサーバーです。

  Vertx vertx = Vertx.newVertx();
  vertx.createHttpServer().requestHandler(new Handler() {
      public void handle(HttpServerRequest req) {
          String file = req.path.equals("/") ? "index.html" : req.path;
          req.response.sendFile("webroot/" + file);
      }
  }).listen(8080);

次のコードは、まったく同じ機能を持ちますがGroovyで書いた場合です:

  def vertx = Vertx.newVertx()
  vertx.createHttpServer().requestHandler { req ->
  def file = req.uri == "/" ? "index.html" : req.uri
  req.response.sendFile "webroot/$file"
  }.listen(8080)
  Future of Vert.x and NHN

Vert.xとNHNの将来

NHN社において我々はVert.xを高く評価しており、公式リリース以前からVert.x開発に注目しています。私たちはVert.xを改善について議論するために2012年6月以来、Vert.xの主な開発者(Tim Fox)と連絡を取っています。例えば、Vert.xの上のSocket.ioについてです。

Socket.ioはNode.jsだけで利用可能でしたが、私たちはJavaに移植し、Gitubの上のVert.xリポジトリにPull Requestを送りhttps://github.com/vert-x/vert.x/pull/320、今まさに、Vert.x-modプロジェクトにマージされるところです。私たちの努力であるsocket.io vert.xモジュールは、NHNで開発しているRTCS 2.0バージョン(vert.x+Socket.io)で使用されます。

Node.jsの人気の理由の一つは、Socket.ioのためであるかもしれず、Vert.xでもSocket.ioを使用できるようになれば、Vert.xの用途は広がるかもしれません。更に、このsocket.io vertxモジュールを埋め込みライブラリとして使用することで、Javaベースのアプリケーション中でsocket.ioを使用できるようになります。

RTCSって何

RTCS(瞬時通信システム)はNHNによって作られたリアル・タイム・ウェブ開発プラットフォームであり、ブラウザとサーバ間のメッセージのリアル・タイム転送を支援します。
RTCSはBaseball 9、Me2Dayチャット、BANDチャットのようなNHNウェブサービスのために使われています。

まとめ

Vert.xの最初のバージョンは2012年5月にリリースされたので、2009年に最初のバージョンがリリースされたNode.jsと比較すると、Vert.xの歴史は非常に短く、参考資料は多くありません。しかし、Vert.xはVMwareに支援されており、CloudFoundryの上で動作することもできます。なので、多くの参考資料がすぐに入手可能になるだろうと期待しています。

著者

Web Platform Development Labのソフトウェア・エンジニア、Seongmin Wooによる。

参照

JJUG CCC 2013 Springに参加してきました #jjug #jjug_ccc

今日は朝早くから、西新宿でJJUG CCC 2013 Springに参加してきました。(まとめ)
久々の1日の勉強会で楽しかったです。あと都庁を間近かで見れてよかった。
ブログ書くまでが勉強会、という基本を新ためて学んだので、書きます。

基調講演-1 Javaのこれからを考える #ccc_k1

変容することが確実なこれからのシステム開発、それに耐え得る「サステイナブルSI」を考える。
途中から入りました(すみません)ので半端なメモです。

  • 資料: http://www.slideshare.net/yusuke/java-jjug-ccc-2013-spring
  • サービス全体がJavaだけで完結することはないが、 Javaはプラットフォーム、Javaはオープン、Javaはエコシステム、である、という特長は活きる。
  • Javaの未来もその変化に追随変化していく
    • Java9(Cloud, Multitenancy, GPU, Jigsaw.)
    • Oracleはコミュニティと上手くやっているのだが、驚きとイノベーションは少ないなあ。
    • JVM上の言語は進化しているし。

基調講演-2 What’s New for JavaFX in JDK 8 #ccc_k2

JavaFX、ちゃんと生きてます!、という話。

  • 資料: http://www.slideshare.net/OracleMiddleJP/jjugwhatsnewforjavafxinjdk8
  • JavaFXイベントハンドラがJava8 Lambda式でわかりやすくなる
  • 「モデナ」というUIテーマが追加。
  • 3Dグラフィック機能追加(ジオメトリ、カメラ、ライト)
  • 質問: iPadJavaFXを去年デモしてたけどあれはどうなった?
    • あれはコンセプトプルーフプロトタイプであって、提供予定はない(キリっ。OSSなので、誰でも可能性がありますよ。いつ参加する?今でしょ!!

Java EE 6 から Java EE 7 に向かって #ccc_h1

Java EE 7の情報。押えておくべき。

  • 資料: http://www.slideshare.net/OracleMiddleJP/java-ee-6-to-java-ee-7
  • プロファイルによるサブセット化はJavaSEの方にも将来的に入るよ。ラズベリーパイ用のJavaはそれを使ってるはず。
  • Java EEAPIは枝刈り(プルーニング)をして、古い・使わないものは整理します。
  • JAXRPC,JAXR,EJB Entity Beanは、Jax-WS, JPAになります。
  • 古いのはそのうち削除しますので移行してください。
  • JavaEEのコンテナ(APサーバ)をJavaSEから立ち上げられるようになります。
    • 各社のAPサーバ製品を共通のJavaEEコンテナと見做してインスタンス化・起動するための共通APIができます。
  • APIについては
    • Concurrency,Batch,JSON,WebSocket→新規
    • JSF2.2,JAX-RS→大幅な変更
  • JSON JSR 353。前とは結構変化した。
    • Streaming API..低レベル(StAXみたいな)
    • Object Mode API…かんたん。Streamig API上に構築(DOMみたいな)
  • EL3.0ではメモリ上のオブジェクト群に対してLINQっぽい操作ができる(例を見る限り、Lambda式が使える、ということに思えたけど…)。

Project Lambda Essential #ccc_h2

基本をしっかりと。これを聞いてからGroovy資料作れたなら楽ができたのにorz。

  • Lambda式の目的は、並列計算の記述を簡単にするため。
    • クロージャとはあえて呼びたくない。なぜなら、ローカル変数をキャプチャしない(実効final)なのでレキシカルスコープとは言えない、関数型(高階型)が無い、など一般的なクロージャと思うとがっかりするよ。
  • Lambda式の「構文」より「並列API(JSR166y、Fork/Join Framework)」の方が重要
  • 2コアのCPUで、100万個の要素の平均分散をparrallellStream()で処理とると半分の時間になる!
    • 100とか200個とかでは意味無いってことね。
  • Lambda式が表すクラスをコンパイルしても.classファイルが生成されない。メモリ上に動的に生成される。またinvokeDynamicが使われている。
  • streamは遅延評価される。
  • 今までstreamは別バイナリだったけどb89でJava8 eaの本体の方に取り込まれ一体化しました。

Type Annotation って何? それを使うとプログラムはどう変わる? #ccc_r53

Java8で新規導入される「型アノテーション」なる言語機能について。
個人的には今回このセッションがツボでした。使いやすいかどうかはともかく、野心的なところがイイね! 個人的要望としてはJava10あたりでASTを変更可能にして欲しいw。

例:

MyObject m = new @Interneed MyObject(); //new対象のクラス
MyObject n = new @NonEmpty @ReadOnly  MyObject(); //new対象のクラス
class Hoge implements @Readonly List<@Readonly String> {} //ジェネリクスの型引数
MyObject xxx = (@NonNull String) myObject; //キャスト
int @ReadOnly[]@ReadOnly[] a = ..
int @ReadOnly[][] a = ..
int []@ReadOnly[] a = ..
  • 何をするものかというと、コード中の型情報に追加情報を指定してやって、それを元に代入やアクセスなどの操作を制限する。実効的にはGroovy 2.1の型チェック拡張みたい。
    • Java8で何らかのASTをアクセスするAPIが提供されるはず。
  • Java8の型アノテーション仕様のサポートは、「型アノテーションを含むコードがコンパイルできますよ」というだけ。驚くべきことにアノテーション自体は1つも標準APIで定義されない。
    • サードパーティさん頼み状態
    • 現時点ではJava8に対応してないが、サードパーティツールの候補一つとして、Java6時代から利用可能な「Checker Framework」というのがある。
      • javaコマンドで実行する。javacではなく。
    • @Interned, @Immutable, @Readonly, ...
  • Checker FrameworkはJava6,7でも実行できるが、以下のような特殊コメントを使う。

//*>>> @ReadOnly...
/*>>> @ReadOnly... */

Groovy2.Xの新機能 #ccc_r25

あてくしの発表。人数多い。JGGUGとは違うのだよ!状態。

しかしまさかのプロジェクタ出力できず。
急遽Windows PCをお借りして2分ロスしてスタート。
PC貸してくださいました方ありがとうござました。
USB接続のRGBコネクタを持ってくるべきだったな。

でも結果としては無事になんとか。(かな?)

失敗から学ぶAPI設計 #ccc_r26

地方における勉強会事情 #ccc_r26

これはツイッターを見てもらった方が良いかも。(ハッシュタグ#ccc_r26)
コミュニティの運営の問題として、様々な話が聞けました。
子供連れイベントいいですね。Scratchとかプログラミンのハンズオンとか。

LT

いろいろ楽しかったです。しかし酒が入ったので記憶があんまりない。

  • 自己生成コードGolf
  • DTrace
  • 破壊的イノベーションw
  • Matzは中部地方では神
  • アーランと寿司
  • いろふさん
  • Play 1 Javaは小数派だが良いものである
  • javapをiPhoneで動かす(違
  • ニセコで寿司?

たいへんおつかれさまでした。運営のJJUGのみなさん、ありがとうございました。是非また参加したいです。

G*ワークショップZ May 2013 - Spockハンズオン5/17

G*ワークショップZ Spockハンズオンのおしらせ。

Spockを試してみたいかたは是非どうぞ。

NTTソフトウェアはオフィスが移転したので、今回から場所がいつもと違います。品川の同じ並びの別のビルです。

オフラインどう書く第九回の問題をJava 8 Lambdaでやってみた。

という記事を書きましたが、今回はJava 8 Lambda式を使って書きなおしてみました。

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

class BusJava8 {
  
  int ceil10(int n) { return (int)(Math.ceil(n/10) * 10); }
  
  int half(int n) { return ceil10(n/2); }
  
  List parse(String input) {
    String[] tmp = input.split(":", 0);
    String p = tmp[0];
    String list = tmp[1];
    return Arrays.asList(Integer.parseInt(p), Arrays.asList(list.split(",", 0)));
  }
  
  int calc(int basePrice, List<String> passengers) {
    Map<String,Function<Integer,Integer>> map = new HashMap<>();
    map.put("An", (Integer it)->it);
    map.put("Ap", (Integer it)->0);
    map.put("Aw", (Integer it)->half(map.get("An").apply(it)));
    map.put("Cn", (Integer it)->half(map.get("An").apply(it)));
    map.put("Cp", (Integer it)->0);
    map.put("Cw", (Integer it)->half(map.get("Cn").apply(it)));
    map.put("In", (Integer it)->half(map.get("An").apply(it)));
    map.put("Ip", (Integer it)->0);
    map.put("Iw", (Integer it)->half(map.get("In").apply(it)));
    Map<String,List<List>> groups =passengers.stream()
      .map(it -> Arrays.asList(it, map.get(it).apply(basePrice)))
      .collect(Collectors.groupingBy(it -> (((String)it.get(0)).substring(0,1))));
    long size = (long)(groups.get("I").size()-groups.get("A").size()*2);
    groups.get("I").sort((a, b) -> (Integer)(a.get(1)) - (Integer)(b.get(1)));
    List<List> tmp = new ArrayList(groups.get("A"));
    tmp.addAll(groups.get("C"));
    tmp.addAll(groups.get("I").stream().limit(size).collect(Collectors.toList()));
    return tmp.stream().mapToInt(it->(Integer)it.get(1)).sum();
  }
  
  static void test(String input, String answer) {
    Java8 b = new Java8();
    List tmp = b.parse(input);
    int basePrice = (int)tmp.get(0);
    List<String> passengers = (List<String>)tmp.get(1);
    int expected = Integer.parseInt(answer);
    int result = b.calc(basePrice, passengers);
    assert result == expected;
  }

  public static void main(String[] args) {
      test("1480:In,An,In,In,In,Iw,Cp,Cw,In,Aw,In,In,Iw,Cn,Aw,Iw", "5920");
  }
}

上では、自分がJava8に全く不慣れなため、非効率・冗長な記述をしているところがあるかもしれません。また、そもそも元のクロージャを駆使したアルゴリズムがGroovy向きだった、など比較としてはフェアではないかもしれませんので、ご留意を。Java 8Lambdaの仕様も未確定だと思いますので、現時点でのeaでの仕様であることも注意ください。

で、感想を言うと、こりゃ書くのたいへんだわ、です。Java8の補完対応IDEを使わなかったこと、などもあると思いますが…。あと結構エラーメッセージが怖いです。たとえば

Java8.java:28: エラー: 不適合な型: 推論変数Rには、不適合な境界があります .collect(Collectors.groupingBy(it -> (((String)(it.get(0))).substring(0,1))));
^
等価制約: Map>>
上限: Map,Object
R,Tが型変数の場合:
メソッド collect(Collector)で宣言されているRはObjectを拡張します
インタフェース Streamで宣言されているTはObjectを拡張します
INT#1,INT#2がintersection型の場合:
INT#1はObject,Serializable,Comparableを拡張します
INT#2はObject,Serializable,Comparableを拡張します

とか。型指定を1つ間違えただけで上のメッセージは…。

他に、Groovyと比べると、例えLambdaを使っても、

  • オペレータオーバーロードが無い
  • Collectionとstreamが別ものである
  • リストやマップリテラルが無い
  • 破壊的ではないリストの結合とかsortとかが無い(見付けられなかっただけかも…)ようなので、メソッドチェインが途切れてしまうのも気になります

などにより、冗長性は高くなるようです。シンプルなケースを除き、高階関数をバリバリ使って関数型ライクに書くのは余り積極的になれない気がしました。リリースまでには多くが解決されてしまうかもしれませんが*1

Lambda式の良い点は、GroovyのCompileStticに比べても、型推論の精度が高いということ。GroovyのCompileStaticでは、クロージャの戻り値の型は推論に寄与してないようで、チェインすると2個目以降に明示指定が必要になります。

さて、速度はどうだったか、などはJJUG CCC 2013 Springのセッションにて(ステマ)。

*1:まあ、lambdaの主眼はマルチコア細粒度並列処理と言われているので、もしそうなら通常の処理にあえて使う必然性もないわけですけれども。

オフラインどう書く第九回の問題をCompileStatic指定してやってみた。

オフラインどう書く第九回の問題をやってみたという記事を書きましたが、今回はCompileStaticアノテーションを使って書きなおしてみました。

import groovy.transform.*

@CompileStatic
int ceil10(int n) { (Math.ceil(n/10) * 10) as Integer }

@CompileStatic
int half(int n) { ceil10(n/2) }

@CompileStatic
List parse(String input) {
  String[] tmp = input.split(':')
  String p = tmp[0]
  String list = tmp[1]
  return [Integer.parseInt(p), list.split(",") as List]
}

@CompileStatic
int calc(int basePrice, List<String> passengers) {
  Map<String,Closure> map
  map = [An:{int it->it},
         Ap:{int it->0},
         Aw:{int it->half((int)map.get('An')(it))},
         Cn:{int it->half((int)map.get('An')(it))},
         Cp:{int it->0},
         Cw:{int it->half((int)map.get('Cn')(it))},
         In:{int it->half((int)map.get('An')(it))},
         Ip:{int it->0},
         Iw:{int it->half((int)map.get('In')(it))},
        ]
  Map<String,List> groups = passengers.collect{String it -> [it, map.get(it)(basePrice)]}.groupBy{List<String> it -> it[0][0]}.withDefault{[]}
  return ((groups.get('A')+groups.get('C')+groups.get('I').sort{
            List<Integer> it -> it[1]
           }.take(groups.get('I').size()-(groups.get('A').size()*2))).sum{List<Integer> it -> it[1]}) as Integer
}

@CompileStatic
void test(String input, String expected) {
  List tmp = parse(input)
  int basePrice = (int)tmp[0]
  List<String> passengers = (List<String>)tmp[1]
  assert Integer.parseInt(expected)==calc(basePrice, passengers)
}

test( "1480:In,An,In,In,In,Iw,Cp,Cw,In,Aw,In,In,Iw,Cn,Aw,Iw", "5920" );

マップの要素指定がプロパティ形式でアクセスできなくなったり、クロージャの暗黙引数が省略できないケース(型推論できない場合)があったり、結構冗長になっていることがわかります。ちなみに上記の書き直しのために、staticalizerは役立ちます。

あと正しいTupleが欲しい。

本記事は、JJUG CCC 2013 Springのステマです。