既存の秘密鍵・証明書からJavaのTrustStore & KeyStoreを作る手順メモ

すぐに忘れそうなので、メモメモ。

KeyStore

openssl pkcs12 -inkey server.key -in server.crt -export -out keystore.pkcs12
keytool -importkeystore -srckeystore keystore.pkcs12 -srcstoretype pkcs12 -destkeystore keystore.jks -destkeypass keypassword -deststorepass storepassword
# 内容を確認
keytool -list -v -keystore keystore.jks

TrustStore

keytool -import -file server.crt -alias mytruststore -keystore truststore.jks

# 内容を確認
keytool -list -v -keystore truststore.jks

IntelliJでRobocodeのRobot開発メモ

毎回忘れて色々試しては時間を食っている気がするのでメモ。
1. Robocode自体のインストール (See http://robowiki.net/wiki/Robocode/Download)
2. IntelliJで適当にprojectを作り、`Project Settings` -> `Modules (Dependencies)` に `JARs or directory` として上記1でインストールしたRobocode配下にある `libs/robocode.jar` を追加
3. http://robowiki.net/wiki/Robocode/My_First_Robot 辺りを参考に適当なRobotを作成
4. IntelliJ上で `Build Project` しておき class files を出力させておく
5. Robocodeの `Options` -> `Preferences` -> `Development Options` に上記4の class files が出力されている root directory を指定
6. あとは普通にRobocode上の `New` 以降で作成した Robot が指定できるようになっているはず

JVM Tool Interfaceを少しさわってみたメモ

ふと、JVM(TM) Tool Interface 1.2.3 に触れたことがないことに気がつき、少しさわってみたのでメモ。

OpenJDKのソースコードjdk/src/share/demo/jvmti の下に幾つか例があるので、それを試してみることに。ちなみに概要は上記URLのドキュメントの各APIの説明の直前まで眺めれば良さそう。APIの種類が多いので少し圧倒されるけど、実際に何か作る際に目的に合ったものを見れば問題無いかと。

今回見てみたのは versionCheck / mtrace の二つ。

  • versionCheck

Agent_OnLoad()でJVMTI_EVENT_VM_INITイベントにコールバック関数をセットして、その中でjvmtiEnvから引っこ抜いたバージョン番号をstdoutに表示するだけのシンプルなもの

Mac OS X上でのビルドは以下のような感じ。ちなみにagent_util/agent_util.cはjdk/src/share/demo/jvmtiが依存しているヘルパー関数群。

$ pwd
/Users/komamitsu/src/openjdk/jdk/src/share/demo/jvmti/versionCheck
$ clang -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin -I../agent_util -c ../agent_util/agent_util.c *.c
$ clang -dynamiclib -L${JAVA_HOME} -o libversionCheck.so *.o

これを利用する場合は以下のように指定する

$ java -agentpath:/Users/komamitsu/src/openjdk/jdk/src/share/demo/jvmti/versionCheck/libversionCheck.so Main
Compile Time JVMTI Version: 1.2.1 (0x30010201)
Run Time JVMTI Version: 1.2.3 (0x30010203)
Hello world
  • mtrace

Javaのメソッド呼び出し回数をトレースするもの。Agent_OnLoad()で色々コールバックを設定するが、面白いのはJVMTI_EVENT_CLASS_FILE_LOAD_HOOKで都度ロードされたクラスファイルの各メソッドを、同梱されているMtrace.javaのメソッドを最初に呼ぶように書き換えている(と思う)。Mtrace.javaのメソッドは当該Cライブラリファイルの関数(この中でメソッド呼び出し回数をカウント)をnative methodとして呼ぶように紐づけられているが、その際、JNIのFindClass()とRegisterNative()を使っている。ちなみにjava_crw_demo/java_crw_demo.c はクラスファイル操作系ヘルパー関数群。

$ pwd
/Users/komamitsu/src/openjdk/jdk/src/share/demo/jvmti/mtrace
$ clang -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin -I../agent_util -I../java_crw_demo -c ../agent_util/agent_util.c ../java_crw_demo/java_crw_demo.c *.c
$ clang -dynamiclib -L${JAVA_HOME} -o libmtrace.so *.o
$ mkdir -p classes
$ javac -d classes Mtrace.java
$ jar cf mtrace.jar *

使い方で少しハマった点としては、FindClass()でMtrace classを探す際、ロードされる前に探しに行くとJVMがクラッシュしてしまう(FindClass()の戻り値がNULLになることを期待してたけど...)。なので -Xbootclasspath/a でmtrace.jarを先にロードする必要あり

$ java -Xbootclasspath/a:/Users/komamitsu/src/openjdk/jdk/src/share/demo/jvmti/mtrace/classes/mtrace.jar -agentpath:/Users/komamitsu/src/openjdk/jdk/src/share/demo/jvmti/mtrace/libmtrace.so  Main
    :
Class java/lang/String 8084 calls
        Method charAt (I)C 3941 calls 3941 returns
        Method length ()I 832 calls 832 returns
        Method equals (Ljava/lang/Object;)Z 543 calls 543 returns
    :

なお、途中で、調査のため-Xcheck:jni -verboseを有効にしたら捗った。特に-verbose指定時に以下のエラーが出たのでJDKのバージョン違いに気がつけた(途中JDK8 -> 7にして試してた)

[Loaded java.lang.ClassFormatError from /Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.UnsupportedClassVersionError from /Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/jre/lib/rt.jar]

MessagePack v07とJacksonの文字列を対象とした性能比較

        int cnt = 800000;

        {
            ObjectMapper mapper = new ObjectMapper();
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            List<String> strList = new ArrayList<String>(cnt);
            for (int i = 0; i < cnt; i++) {
                strList.add("01234567");
            }

            System.gc();
            Thread.sleep(3000);

            long start = System.currentTimeMillis();
            mapper.writeValue(outputStream, strList);
            System.out.println("Jackson(Serialize, String): " + (System.currentTimeMillis() - start) + "ms");

            System.gc();
            Thread.sleep(3000);

            start = System.currentTimeMillis();
            mapper.readValue(outputStream.toByteArray(), new TypeReference<List<String>>() {});
            System.out.println("Jackson(Deserialize, String): " + (System.currentTimeMillis() - start) + "ms");
        }

        {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            MessagePacker packer = org.msgpack.core.MessagePack.newDefaultPacker(outputStream);

            System.gc();
            Thread.sleep(3000);

            long start = System.currentTimeMillis();
            for (int i = 0; i < cnt; i++) {
                packer.packString("01234567");
            }
            packer.flush();

            System.out.println("MessagePack(Serialize, String): " + (System.currentTimeMillis() - start) + "ms");

            MessageUnpacker unpacker = org.msgpack.core.MessagePack.newDefaultUnpacker(outputStream.toByteArray());

            System.gc();
            Thread.sleep(3000);

            start = System.currentTimeMillis();
            for (int i = 0; i < cnt; i++) {
                unpacker.unpackString();
            }
            System.out.println("MessagePack(Deserialize, String): " + (System.currentTimeMillis() - start) + "ms");
        }

int cnt = 1000000;

Jackson(Serialize, String): 71ms
Jackson(Deserialize, String): 60ms
MessagePack(Serialize, String): 96ms
MessagePack(Deserialize, String): 82ms

int cnt = 1000000;

Jackson(Serialize, String): 103ms
Jackson(Deserialize, String): 246ms
MessagePack(Serialize, String): 192ms
MessagePack(Deserialize, String): 158ms

int cnt = 10000000;

Jackson(Serialize, String): 574ms
Jackson(Deserialize, String): 7259ms
MessagePack(Serialize, String): 1316ms
MessagePack(Deserialize, String): 1049ms

ByteBufferへのコピー速度比較

JavaでByteBufferへのコピーを行う際の速度を簡単に比較してみました。java.nio.ByteBuffer#put(byte)で1byteずつ書き込んでいくのでなければ大体同じですね。

Heap上のbyte arrayをコピー

java.nio.ByteBuffer#put(byte[], int, int)

        byte[] bs = new byte[256 * 1024 * 1024];
        ByteBuffer src = ByteBuffer.wrap(bs);
        ByteBuffer dst = ByteBuffer.allocate(bs.length);
        long start = System.currentTimeMillis();


        dst.put(src.array(), 0, src.remaining());
        System.out.println("heap.byte_array: ByteBuffer.put(byte[], int, int): " + (System.currentTimeMillis() - start));

=> heap.byte_array: ByteBuffer.put(byte[], int, int): 73

java.nio.ByteBuffer#put(java.nio.ByteBuffer)

        dst.put(src);
        System.out.println("heap.byte_array: ByteBuffer.put(java.nio.ByteBuffer): " + (System.currentTimeMillis() - start));

=> heap.byte_array: ByteBuffer.put(java.nio.ByteBuffer): 69

java.nio.ByteBuffer#put(byte) with loop

        for (int i = 0; i < src.remaining(); i++)
            dst.put(src.get());
        System.out.println("heap.byte_array: ByteBuffer.put(byte): " + (System.currentTimeMillis() - start));

=> heap.byte_array: ByteBuffer.put(byte): 380

Heap上のByteBufferをコピー

java.nio.ByteBuffer#put(byte[], int, int)

        ByteBuffer src = ByteBuffer.allocate(256 * 1024 * 1024);
        ByteBuffer dst = ByteBuffer.allocate(256 * 1024 * 1024);
        long start = System.currentTimeMillis();

        dst.put(src.array(), 0, src.remaining());
        System.out.println("heap.bb: ByteBuffer.put(byte[], int, int): " + (System.currentTimeMillis() - start));

=> heap.bb: ByteBuffer.put(byte[], int, int): 72

java.nio.ByteBuffer#put(java.nio.ByteBuffer)

        dst.put(src);
        System.out.println("heap.bb: ByteBuffer.put(java.nio.ByteBuffer): " + (System.currentTimeMillis() - start));

=> heap.bb: ByteBuffer.put(java.nio.ByteBuffer): 70

java.nio.ByteBuffer#put(byte) with loop

        for (int i = 0; i < src.remaining(); i++)
            dst.put(src.get());
        System.out.println("heap.bb: ByteBuffer.put(byte): " + (System.currentTimeMillis() - start));

Off heap上のByteBufferをコピー

java.nio.ByteBuffer#put(byte[], int, int)

        ByteBuffer src = ByteBuffer.allocateDirect(256 * 1024 * 1024);
        ByteBuffer dst = ByteBuffer.allocate(256 * 1024 * 1024);
        long start = System.currentTimeMillis();

        dst.put(src.array(), 0, src.remaining());
        System.out.println("off-heap.bb: ByteBuffer.put(byte[], int, int): " + (System.currentTimeMillis() - start));

=> java.lang.UnsupportedOperationException (it doesn't have array inside)

java.nio.ByteBuffer#put(java.nio.ByteBuffer)

        dst.put(src);
        System.out.println("off-heap.bb: ByteBuffer.put(java.nio.ByteBuffer): " + (System.currentTimeMillis() - start));

=> off-heap.bb: ByteBuffer.put(java.nio.ByteBuffer): 72

java.nio.ByteBuffer#put(byte) with loop

        for (int i = 0; i < src.remaining(); i++)
            dst.put(src.get());
        System.out.println("off-heap.bb: ByteBuffer.put(byte): " + (System.currentTimeMillis() - start));

=> off-heap.bb: ByteBuffer.put(byte): 358

TypeProfileが取得される場合、所得されない場合について

JavaではJITコンパイルに関連するTypeProfileという機構がありますが、これがどのような状況で取得されるのかちょっと興味があったので簡単に試してみました。


komamitsu/TypeProfileTest · GitHub

Childというinterfaceを継承するChildImplA/Bという二つのclassがあり、MainXxxxというclassからChild/ChildImplA/Bの扱い方を微妙に変えて、TypeProfileが取得される挙動を見てみました。

https://github.com/komamitsu/TypeProfileTest/tree/master/org/komamitsu/tptest にMainXxxx.java群が置いてありますが概要から言うと、"あるメソッド内で、あるオブジェクトが `new` によって生成されて、そのオブジェクトが当該メソッド内のlocal変数経由で呼ばれる場合のみTypeProfileが取得されない" ように見えました。具体的には以下です。

TypeProfileTest/MainWithoutFactoryMethodAndImportingB.java at master · komamitsu/TypeProfileTest · GitHub

package org.komamitsu.tptest;
import org.komamitsu.tptest.child.Child;
import org.komamitsu.tptest.child.ChildImplA;
import org.komamitsu.tptest.child.ChildImplB;

public class MainWithoutFactoryMethodAndImportingB {
    public static void main(String argv[]) {
        Child c = new ChildImplA();
        for (int i = 0; i < 10000000; i++) {
            c.exec();
        }
        System.out.println(c.getCount());
    }
}

TypeProfileTest/WithoutFactoryMethodAndImportingB at master · komamitsu/TypeProfileTest · GitHub

$ java -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining org.komamitsu.tptest.MainWithoutFactoryMethodAndImportingB
     62    1    b        java.lang.String::hashCode (55 bytes)
     72    2    b        java.lang.String::indexOf (70 bytes)
                            @ 66   java.lang.String::indexOfSupplementary (71 bytes)   too big
     82    3    b        org.komamitsu.tptest.child.ChildImplA::exec (11 bytes)
     83    4 %  b        org.komamitsu.tptest.MainWithoutFactoryMethodAndImportingB::main @ 10 (41 bytes)
                            @ 17   org.komamitsu.tptest.child.ChildImplA::exec (11 bytes)   inline (hot)
     85    4 %           org.komamitsu.tptest.MainWithoutFactoryMethodAndImportingB::main @ -2 (41 bytes)   made not entrant
10000000

もうちょっと色々なケースで試してみたけれど、ひとまずはここまでで。

JavaのEscape AnalysisでNoEscapeがGlobalEscapeになってしまうケースの検証

Java ™ HotSpot Virtual Machine Performance Enhancements

Escape analysis is supported and enabled by default in Java SE 6u23 and later.

ということで最近のJVMでは有効になっているEscape analysisですが、こんな記事を見つけました。


Richard Burnison - Escape Analysis & Stack Allocated Objects

NoEscapeについて説明しているくだりで

Important to note, however, is that objects requiring finalizer execution are considered GlobalEscape and will not be stack-bound.

という記述があり、ちょっと気になったので試してみました。

$ java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

まずは普通に。

public class Main {
    void run() {
        int totalLen = 0;
        for (int i = 0; i < 100000000; i++) {
            String s = new String("hello");
            totalLen += s.length();
        }
        System.out.println(totalLen);
    }

    public static void main(String argv[]) {
        Main main = new Main();
        main.run();
    }
}
  • Escape analysis無し
$ java -Xms512M -Xmx512M -XX:+PrintGCDetails -verbose:gc -XX:-DoEscapeAnalysis Main
[GC [PSYoungGen: 132096K->432K(153600K)] 132096K->440K(503296K), 0.0030680 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] 
50000000
Heap
 PSYoungGen      total 153600K, used 111393K [0x00000007f5500000, 0x0000000800000000, 0x0000000800000000)
  eden space 132096K, 84% used [0x00000007f5500000,0x00000007fc15c698,0x00000007fd600000)
  from space 21504K, 2% used [0x00000007fd600000,0x00000007fd66c010,0x00000007feb00000)
  to   space 21504K, 0% used [0x00000007feb00000,0x00000007feb00000,0x0000000800000000)
 ParOldGen       total 349696K, used 8K [0x00000007dff80000, 0x00000007f5500000, 0x00000007f5500000)
  object space 349696K, 0% used [0x00000007dff80000,0x00000007dff82000,0x00000007f5500000)
 PSPermGen       total 21504K, used 2554K [0x00000007dad80000, 0x00000007dc280000, 0x00000007dff80000)
  object space 21504K, 11% used [0x00000007dad80000,0x00000007daffebf8,0x00000007dc280000)
  • Escape analysis有り
$ java -Xms512M -Xmx512M -XX:+PrintGCDetails -verbose:gc -XX:+DoEscapeAnalysis Main
50000000
Heap
 PSYoungGen      total 153600K, used 5284K [0x00000007f5500000, 0x0000000800000000, 0x0000000800000000)
  eden space 132096K, 4% used [0x00000007f5500000,0x00000007f5a29028,0x00000007fd600000)
  from space 21504K, 0% used [0x00000007feb00000,0x00000007feb00000,0x0000000800000000)
  to   space 21504K, 0% used [0x00000007fd600000,0x00000007fd600000,0x00000007feb00000)
 ParOldGen       total 349696K, used 0K [0x00000007dff80000, 0x00000007f5500000, 0x00000007f5500000)
  object space 349696K, 0% used [0x00000007dff80000,0x00000007dff80000,0x00000007f5500000)
 PSPermGen       total 21504K, used 2552K [0x00000007dad80000, 0x00000007dc280000, 0x00000007dff80000)
  object space 21504K, 11% used [0x00000007dad80000,0x00000007daffe060,0x00000007dc280000)

Escape analysis有りの場合、Eden領域の使用量が少なくHeapではなくStack allocationになっているものと推測。

では、finalize() 実装版

public class Main {
    static int finalizeCount = 0;

    void run() {
        int totalLen = 0;
        for (int i = 0; i < 10000000; i++) {
            String s = new String("hello");
            totalLen += s.length();
        }
        System.out.println(totalLen);
        System.out.println(finalizeCount);
    }

    @Override
    protected void finalize() throws Throwable {
        finalizeCount++;
        super.finalize();
    }

    public static void main(String argv[]) {
        Main main = new Main();
        main.run();
    }
}

これをEA有効にして実行してみると

$ java -Xms512M -Xmx512M -XX:+PrintGCDetails -verbose:gc -XX:+DoEscapeAnalysis Main
50000000
0
Heap
 PSYoungGen      total 153600K, used 5284K [0x00000007f5500000, 0x0000000800000000, 0x0000000800000000)
  eden space 132096K, 4% used [0x00000007f5500000,0x00000007f5a29028,0x00000007fd600000)
  from space 21504K, 0% used [0x00000007feb00000,0x00000007feb00000,0x0000000800000000)
  to   space 21504K, 0% used [0x00000007fd600000,0x00000007fd600000,0x00000007feb00000)
 ParOldGen       total 349696K, used 0K [0x00000007dff80000, 0x00000007f5500000, 0x00000007f5500000)
  object space 349696K, 0% used [0x00000007dff80000,0x00000007dff80000,0x00000007f5500000)
 PSPermGen       total 21504K, used 2552K [0x00000007dad80000, 0x00000007dc280000, 0x00000007dff80000)
  object space 21504K, 11% used [0x00000007dad80000,0x00000007daffe2f0,0x00000007dc280000)

と、finalize() 実装前と変わらないので、"objects requiring finalizer execution are considered GlobalEscape and will not be stack-bound" というのは(少なくとも1.7.0_45では)気にしなくて良さそうかなぁと思います。