JIT コンパイラ関連オプションの挙動

Oracle Java 6 での JIT コンパイラ関連のオプションとその挙動についての記述。とはいえ大きく挙動を変えられるような実装ではありませんので以下の 3 つを押さえておけば良いかと思います。

-XX:CompileThreshold=10000 JIT コンパイルを行うまでのメソッド呼び出し回数または分岐回数
-XX:+CITime プログラム終了時に JIT コンパイラの実行統計を出力 (JDK 1.4.0 以降)
-XX:+PrintCompilation JIT コンパイル実行時にメッセージを出力

他に -Xint (JIT コンパイルを行わない), -Xbatch (フォアグラウンドで JIT コンパイルを行う) オプションもありますが、これらは JIT コンパイラの実装が貧弱で混沌としていた時代の名残です。今時は興味実行以外での利用機会もないので無視します。

実行サンプルに末尾再帰でフィボナッチ数 F_{(10000)} を求めるプログラムについて。メソッド fibonacci() は引数 n\geq 0 に対して合計値がそのフィボナッチ数となる 2 つの要素を返します。

import java.math.*;
public class A{
  private static final BigDecimal[] fibonacci(int n){
    if(n == 0){
      return new BigDecimal[]{ BigDecimal.ZERO, BigDecimal.ZERO };
    }
    if(n == 1){
      return new BigDecimal[]{ BigDecimal.ZERO, BigDecimal.ONE };
    }
    BigDecimal[] f = fibonacci(n - 1);
    return new BigDecimal[]{ f[1], f[0].add(f[1]) };
  }
  public static void main(String[] args){
    long t0 = System.nanoTime();
    BigDecimal[] f = fibonacci(10000);
    System.out.printf("%.3fms%n", (System.nanoTime() - t0) / 1000000.0);
    System.out.printf("%s%n", f[0].add(f[1]));
    return;
  }
}
実行統計の出力

-XX:+CITime オプションを使用するとプログラム終了時に JIT コンパイラの実行統計が出力されます。

torao@safran$ java -XX:+CITime A
28.132ms
5443837311356528133…97501

Accumulated compiler times (for compiled methods only)
------------------------------------------------
  Total compilation time   :  0.046 s
    Standard compilation   :  0.046 s, Average : 0.004
    On stack replacement   :  0.000 s, Average : nan

  Total compiled bytecodes :   2212 bytes
    Standard compilation   :   2212 bytes
    On stack replacement   :      0 bytes
  Average compilation speed:  48146 bytes/s

  nmethod code size        :   9408 bytes
  nmethod total size       :  17256 bytes
コンパイルごとの出力

-XX:+PrintCompilation オプションは JIT コンパイルが行われるたびにメッセージを表示します。以下の実行では fibonacci() メソッドと共に実行回数の多い BigDecimal のメソッドがコンパイルされています。

torao@safran$ java -XX:+PrintCompilation A
     74   1       A::fibonacci (73 bytes)
     81   2       java.math.BigInteger::add (178 bytes)
     93   3       java.lang.Object::<init> (1 bytes)
     95   4       java.lang.Number::<init> (5 bytes)
    103   5       java.math.BigDecimal::<init> (27 bytes)
31.522ms
    125   6       java.math.BigDecimal::add (302 bytes)
    135   7       java.math.MutableBigInteger::mulsub (110 bytes)
    138   8       java.math.MutableBigInteger::primitiveLeftShift (89 bytes)
    142   9       java.math.BigInteger::<init> (24 bytes)
    145  10       java.math.MutableBigInteger::unsignedLongCompare (20 bytes)
    147  11       java.math.MutableBigInteger::divideMagnitude (594 bytes)
    163  12       java.math.MutableBigInteger::normalize (91 bytes)
5443837311356528133…97501
コンパイルしきい値の調整

-XX:CompileThreshold オプションを使用して JIT コンパイルの行われるタイミングを調整することができます。デフォルトの 10000 を基準に、アプリケーション起動からなるべく早めに JIT コンパイルを済ませたい場合は少ない数値を、JIT コンパイルの影響をなるべく分散させたい場合は大きな値を指定します。以下の例では 1 を指定しているのでブートストラップからほとんどのメソッドが JIT コンパイルされています。

torao@safran$ java -XX:CompileThreshold=1 -XX:+CITime -XX:+PrintCompilation A
---   n   java.lang.Thread::currentThread (static)
---   n   java.lang.System::arraycopy (static)
---   n   java.security.AccessController::getStackAccessControlContext (static)
---   n   java.security.AccessController::getInheritedAccessControlContext (static)
---   n   java.lang.Thread::setPriority0
     81   2       java.lang.Object::<init> (1 bytes)
     85   3       java.lang.Thread::init (193 bytes)
…
---   n   java.lang.Double::longBitsToDouble (static)
---   n   java.lang.Float::floatToRawIntBits (static)
---   n   java.io.FileOutputStream::writeBytes
236.170ms
5443837311356528133…97501

Accumulated compiler times (for compiled methods only)
------------------------------------------------
  Total compilation time   :  2.239 s
    Standard compilation   :  2.239 s, Average : 0.003
    On stack replacement   :  0.000 s, Average : nan

  Total compiled bytecodes :  79292 bytes
    Standard compilation   :  79292 bytes
    On stack replacement   :      0 bytes
  Average compilation speed:  35407 bytes/s

  nmethod code size        : 362752 bytes
  nmethod total size       : 688752 bytes

Kestrel クイックスタート

KestrelTwitter のバックエンドで膨大なリアルタイムデータを処理している分散メッセージキューシステムです。キューに特化して高速性とコンパクト性を優先した設計となっており、JMS で規定されているようなエンタープライズ向けメッセージキューとは方向性がかなり異なります。

JMS Kestrel
トランザクション あり なし
配信形式 Point to Point,
Publisher/Subscriber
Point to Point
FIFO 強い順序付け 弱い順序付け

元々 Twitter では Starling (ムクドリ) という名で Ruby によるメッセージキューを実装していましたが、増え続けるトラフィックを捌ききれず何度もクラッシュしていたため Scala で書きなおしたという経緯があります (Twitter jilts Ruby for Scala • The Register)。memcached プロトコルを使用するのもその時の名残です。

バージョン kestrel-2.1.4
リポジトリ git://github.com/robey/kestrel.git
ライセンス Apache Licence 2
実行環境 Java SE 6 以上
プロトコル memcached
永続化 有 (ジャーナリング)
分散化 キュー名のハッシュ値による分散 (クライアントライブラリ)
冗長化 未対応?

特徴

  • Kestrel のキューは名前によって区別される (この名前がジャーナルファイル名ともなる)。
  • それぞれのキュー内ではメッセージの順序性が保たれている。ただし複数ノードにまたがった参照では順序性は保証されない。
  • メッセージの配信はメモリで動作するが、データ欠損なしにシャットダウンや移動が行えるようジャーナリングされている。
  • Kestrel ノードのクラスタmemcachedクラスタと似ている。
  • アプリケーションへの組み込みが可能。

ダウンロードから起動まで

現在の最新版 Kestrel 2.1.4 を CentOS 5.6 上で起動します。

  1. kestrel-2.1.4.zip をダウンロードして解凍。
  2. 解凍後のディレクトリ kestrel-2.1.4/ を /usr/local/kestrel へ移動。これは起動シェルが /usr/local/kestrel/current 前提で動くため。
  3. シンボリックリンク /usr/local/kestrel/current を作成し /usr/local/kestrel-2.1.4 へ向ける。
  4. /usr/local/kestrel/current/scripts/kestrel.sh に実行権限を与える。
torao@clove$ wget "http://robey.github.com/kestrel/download/kestrel-2.1.4.zip"
 --2012-01-02 20:00:01--  http://robey.github.com/kestrel/download/kestrel-2.1.4.zip
Resolving robey.github.com... 207.97.227.245
Connecting to robey.github.com|207.97.227.245|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 17368814 (17M) [application/zip]
Saving to: `kestrel-2.1.4.zip'

100%[================================================>] 17,368,814   529K/s   in 35s

2012-01-02 20:00:36 (488 KB/s) - `kestrel-2.1.4.zip' saved [17368814/17368814]
torao@clove$ sudo unzip kestrel-2.1.4.zip
[sudo] password for torao:
Archive:  kestrel-2.1.4.zip
  inflating: kestrel-2.1.4/config/development.scala
  inflating: kestrel-2.1.4/config/production.scalatorao@clove$ sudo mkdir /usr/local/kestrel
[sudo] password for torao:
torao@clove$ sudo mv kestrel-2.1.4 /usr/local/kestrel/
torao@clove$cd /usr/local/kestrel
torao@clove$ sudo ln -s kestrel-2.1.4 current
torao@clove$ sudo chmod 755 kestrel-2.1.4/scripts/*.sh

さっそく起動したいところですがデフォルトの script/kestrel.sh では daemon コマンドを使用しています。RedHat 系の CentOSdaemon コマンドがありませんので起動シェルの中身を参考に直接 Java コマンドを実行します。

torao@clove$ sudo java -Xmx4096m -Xms1024m -XX:NewSize=768m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -Xloggc:/var/log/kestrel/gc.log -XX:ErrorFile=/var/log/kestrel/java_error%p.log -server -Dstage=production -jar kestrel-2.1.4.jar &

長々としていますが殆どが GC やヒープの使い方に関するオプションですので、多分 java -Dstage=production -jar kestrel-2.1.4 & だけでも動くと思います。

起動した Kestrel は 22133 ポートで接続待機しています。telnet で接続し memcached コマンドを実行してみます (外部から接続する場合はファイアウォールで接続許可を与えて下さい)。コマンドについては Kestrel Github Document の Memcache commands に記述されています。

torao@safran$ telnet clove 22133
Trying 192.168.51.0...
Connected to clove.bjorfuan.com (192.168.51.0).
Escape character is '^]'.
set torao 0 0 5
ABCDE
STORED
set torao 0 0 10
0123456789
STORED
get torao
VALUE torao 0 5
ABCDE
END
get torao
VALUE torao 0 10
0123456789
END
get torao
END
get torao/t=1000
END
quit
Connection closed by foreign host.

問題なさそうです。

Java HotSpot VM Options

以下 Java HotSpot VM Options の勝手翻訳と、追加で原文には載っていないオプションについて。Oracle JDK 6 を対象とした内容となっています。

このドキュメントは Java HotSpot 仮想マシンのパフォーマンス特性に影響を与える一般的なコマンドラインオプションと環境変数に関する情報を提供します。特に断りのない限りこのドキュメントのすべての情報は Java HotSpot Client VMJava HotSpot Server VM の両方に適用されます。1.3.0 より古い JDKJava HotSpot VM を移植したいユーザは Java HotSpot Equivalents of Exact VM Flags を参照して下さい。

Java HotSpot VM オプションのカテゴリ

Java HotSpot VM が認識する標準オプションは Windows, Solaris, LinuxJava アプリケーション起動リファレンスページで詳述しています。このドキュメントでは Java HotSpot VM が認識する非標準オプションを扱っています。

  • -X で始まるオプションは非標準 (全ての VM 実装でのサポートが保証されていない) であり、今後の JDK リリースで予告なしに変更される場合があります。
  • -XX で記述しているオプションは安定版ではなく気軽な利用は推奨できません。これらのオプションは予告なく変更されることがあります。

いくつかの有用な -XX オプション

デフォルト値の一覧は Java SE 6 for Solaris SPARC の -server 説明にあります。一部のオプションはアーキテクチャ/OS/JVM バージョンごとに異なる可能性があります。異なるデフォルト値を持つプラットフォームは説明に記載しています。

  • ブール値のオプションは -XX:+<option> でオン、-XX:-<option> でオフとなります。
  • 数値オプションは -XX:<option>=<number> で指定できます。数値は 'm' または 'M' でメガバイト、'k' または 'K' でキロバイト、'g' または 'G' でギガバイトを含むことができます (例えば 32k は 32768 と等価)。
  • 文字列オプションは -XX:<option>=<string> で指定し、通常ファイルやパス、コマンドリストを指定します。

管理可能として記述されているフラグは JConsole と Java の管理インターフェース (com.sun.management.HotSpotDiagnosticMXBean API) を通じて動的に書き換え可能です。これは Monitoring and Managing Java SE 6 Platform Applications の図3に例示しています。また管理可能フラグは http://java.sun.com/javase/6/docs/technotes/tools/share/jinfo.html を通じても設定可能です。

以下のオプションは大まかに 3 つのカテゴリに分けています。

振る舞いオプション
オプションとデフォルト値 説明
-XX:-AllowUserSignalHandlers アプリケーションがシグナルハンドラをインストールする際に警告を出さない (Solaris, Linuxのみ)。
-XX:AltStackSize=16384 代替シグナルスタックのサイズ (kB単位) を設定 (Solarisのみ)(5.0で削除)
-XX:-DisableExplicitGC アプリケーションによる System.gc() コールを無効化。JVM は必要に応じて GC を実行する。
-XX:+FailOverToOldVerifier 新しい型チェッカーが失敗した時に古いベリファイアにフェールオーバーする (6で導入)。
-XX:+HandlePromotionFailure 最も Young 世代のコレクションが生存オブジェクトの完全な昇進の保証を必要としない (1.4.2 update 11 で導入) [5.0 及びそれ以前: false]
-XX:+MaxFDLimit ファイル記述子の最大数を増加 (Solarisのみ)。
-XX:PreBlockSpin=10 -XX:+UseSpinning で使用するカウント変数をスピン。オペレーティングシステムのスレッド同期コードに入る前に最大スピンイテレーションを制御できるようにする (1.4.2で導入)。
-XX:-RelaxAccessControlCheck ベリファイアによるアクセス制御チェックを緩和 (6で導入)。
-XX:+ScavengeBeforeFullGC Full GC の前に Young 世代 GC を実行する (1.4.1で導入)。
-XX:+UseAltSigs VM 内部シグナル SIGUSR1, SIGUSR2 の代わりに振替シグナルを使用する (1.3.1 update 9, 1.4.1で導入) (Solarisのみ)。
-XX:+UseBoundThreads カーネルスレッドにユーザレベルスレッドをバインドする (Solarisのみ)。
-XX:-UseConcMarkSweepGC Old 世代に対して concurrent mark-sweep GC を使用する (1.4.1で導入)
-XX:+UseGCOverheadLimit OutOfMemory エラーがスローされる前に GC に費やされている VM 時間の割合を制限するポリシーを使用する (6で導入)。
-XX:+UseLWPSynchronization スレッドベースの同期の代わりに Light Weight Process ベースの同期を使用する (1.4.0で導入) (Solarisのみ)。
-XX:-UseParallelGC Young GC のために並列 GC を使用する (1.4.1で導入)。
-XX:-UseParallelOldGC Full GC のために並列 GC を使用する。-XX:+UseParallelGC を設定にすると自動的に有効となる (5.0 update 6で導入)
-XX:-UseSerialGC 直列 GC を使用する (5.0で導入)。
-XX:-UseSpinning OS スレッド同期コードに入る前に Java モニタ上のネイティブスピンを有効にする (1.4.2と 5.0にのみ関連) [1.4.2マルチプロセッサ Windows:true]。
-XX:+UseTLAB スレッドローカルなオブジェクトの割り当てを行う (1.4.0より前に UseTLE と知られ 1.4.0 で導入) [1.4.2 またはそれ以前, x86 または -client: false]
-XX:+UseSplitVerifier StackMapTable 属性を持つ新しい型チェッカーを使用する (5.0で導入) [5.0:false]。
-XX:+UseThreadPriorities ネイティブスレッドの優先順位を使用する。
-XX:+UseVMInterruptibleIO OS_INTRPT に対して I/O 操作で EINTR を発生させる*1 (6で導入) (Solarisのみ)。
パフォーマンスオプション
オプションとデフォルト値 説明
XX:+AggressiveOpts 今後のリリースでデフォルトとなる予定のポイントパフォーマンスコンパイラ最適化をオンにします (5.0 update 6で導入)。
-XX:CompileThreshold=10000 コンパイルを行うまでのメソッド呼び出し/分岐の回数。 [-client: 1,500]
-XX:LargePageSizeInBytes=4M Java ヒープに対して使用されるラージページのサイズを設定 (14.0 update 1 で導入)。[AMD64: 2m]
-XX:MaxHeapFreeRatio=70 縮小を避けるための GC 後の空きヒープの最大パーセンテージ。
-XX:MaxNewSize=size New 世代の最大サイズ (バイト単位)。1.4 より MaxNewSize は NewRatio の関数として計算される [1.3.1 SPARC:32m; 1.3.1 x86:2.5m]
-XX:MaxPermSize=64M Permanent 世代のサイズ。[5.0 およびそれ以降:64bit VM は 30% より大きくスケール; 1.4 AMD64:96M; 1.3.1 -client:32M]
-XX:MinHeapFreeRatio=40 縮小を避けるための GC 後の空きヒープの最小パーセンテージ。
-XX:NewRatio=2 New/Old 世代サイズの比率。 [SPARC -client:8; x86 -server:8; x86 -client:12] -client:4 (1.3), 8(1.3.1+), x86:12]
-XX:NewSize=2.125m New 世代のデフォルトサイズ (バイト単位) [5.0およびそれ以降:64bit VM は 30% より大きくスケール; x86:1m; x86, 5.0およびそれ以前:640k]
-XX:ReservedCodeCacheSize=32M 予約コードキャッシュサイズ (バイト単位) - 最大コードキャッシュサイズ [Solaris 64bit, AMD64 および -server x86:48m; 1.5.0_06 とそれ以前, Solaris 64bit および AMD64:1024m]
-XX:SurvivorRatio=8 Eden/Survivor領域のサイズの比率 [Solaris AMD64:6; SPARC in 1.3.1:25; その他 Solaris プラットフォーム in 5.0 またはそれ以前:32]
-XX:TargetSurvivorRatio=50 Young GC 後に使用される Survivor 領域の望ましい割合。
-XX:ThreadStackSize=512 スレッドスタックサイズ (Kバイト単位)。(0はデフォルトスタックサイズを使用) [SPARC:512; Solaris x86:320 (5.0以前では 256); SPARC 64bit:1024; Linux AMD64:1024 (5.0以前は0); それ以外は0]
-XX:+UseBiasedLocking バイアスロックを有効にする。詳細についてはJava Tuning White Paper参照。(5.0 update 6 で導入) [5.0:false]
-XX:+UseFastAccessorMethods Get<Primitive>Field の最適化版を使用。
-XX:-UseISM 緊密共有メモリ (ISM) を使用する [Solaris以外のプラットフォームでは無効]。詳細は http://www.oracle.com/technetwork/java/ism-139376.html 参照。
-XX:+UseLargePages ラージページメモリを使用する (5.0 update 5 で導入)。詳細は Java Support for Large Memory Pages 参照。
-XX:+UseMPSS ヒープに対して w/4MB ページの複数ページサイズサポートを使用する。ISM の必要性を置き換えるためにこのオプションを使用しないで下さい (1.4.0 update 1 で導入, Solaris 9 以降が対象) [1.4.1 以前:false]
-XX:+UseStringCache 共通的に割り当てられた文字列のキャッシュを有効。
-XX:AllocatePrefetchLines=1 JIT コンパイルされたコードのプリフェッチ命令を使用して、最後のオブジェクト割り当て後にロードするためのキャッシュライン数。最後に割り当てられたオブジェクトが 1 インスタンスの場合のデフォルト値は 1、配列ならば 3。
-XX:AllocatePrefetchStyle=1 プリフェッチ命令に対して生成されたコードのスタイル。0 - どのようなプリフェッチ命令も生成されない、1 - それぞれの割り当て後にプリフェッチ命令を実行、2 - プリフェッチ命令が実行された時、ゲートに TLAB 割り当てウォーターマークポインターを使用する。
-XX:+UseCompressedStrings 純粋な ASCII として表現できる文字列に対して byte[] を使用する (Java 6 update 21 パフォーマンスリリースで導入)。
-XX:+OptimizeStringConcat 文字列連結操作を可能な限り最適化する (Java 6 Update 20 で導入)。
デバッグオプション
オプションとデフォルト値 説明
-XX:-CITime JIT コンパイラで費やされた時間を出力 (1.4.0で導入)
-XX:ErrorFile=./hs_err_pid<pid>.log エラーが発生した時にエラーデータを保存するファイル (6で導入)。
-XX:-ExtendedDTraceProbes パフォーマンスに影響を与える DTrace Probes in HotSpot VM を有効にする (6で導入, Solaris のみ)。
-XX:HeapDumpPath=./java_pid<pid>.hprof ヒープダンプのディレクトリまたはファイル名へのパス。管理可能 (1.4.2 update 12, 5.0 update 7 で導入)。
-XX:-HeapDumpOnOutOfMemoryError java.lang.OutOfMemoryErrorのがスローされたときにファイルにヒープをダンプする。管理可能 (1.4.2u12, 5.0u7で導入)。
-XX:onError="<cmd args>; <cmd args>" 致命的なエラーが発生した時にユーザー定義のコマンドを実行する (1.4.2u9で導入)。
-XX:OnOutOfMemoryError="<cmd args>; <cmd args>" 最初に OutOfMemoryError がスローされたときにユーザー定義のコマンドを実行する (1.4.2u12, 6 で導入)。
-XX:-PrintClassHistogram [CTRL]+[BREAK] キーでクラスインスタンスヒストグラムを出力。管理可能 (1.4.2で導入)。jmap - Memory Map コマンドは同等の機能を提供する。
-XX:-PrintConcurrentLocks [CTRL]+[BREAK] スレッドダンプで java.util.concurrent ロックを出力する。管理可能 (6で導入)。jstack - Stack Trace コマンドは同等の機能を提供する。
-XX:-PrintCommandLineFlags コマンドラインに現れたフラグを出力する (5.0で導入)。
-XX:-PrintCompilation メソッドがコンパイルされた時にメッセージを出力。
-XX:-PrintGC ガベージコレクションでメッセージを出力。管理可能
-XX:-PrintGCDetails ガベージコレクションで詳細を出力。管理可能 (1.4.0で導入)。
-XX:-PrintGCTimeStamps ガベージコレクションでタイムスタンプを出力。管理可能 (1.4.0で導入) (プロセス起動時からの秒数で出力されるので使うなら -XX:+PrintGCDateStamps の方がお勧め)
-XX:-PrintTenuringDistribution tenuring (殿堂入り) 年齢情報を出力。
-XX:-TraceClassLoading クラスのロードをトレース。
-XX:-TraceClassLoadingPreorder (ロードされていない) 参照によってロードされたすべてのクラスをトレース (1.4.2で導入)。
-XX:-TraceClassResolution 定数プールの消散をトレース (1.4.2で導入)。
-XX:-TraceClassUnloading クラスのアンロードをトレース。
-XX:-TraceLoaderConstraints ローダ制約の記録をトレース (6で導入)。
-XX:+PerfSaveDataToFile 終了時に jvmstat のバイナリデータを保存。
-XX:ParallelGCThreads= Young および Old 並列 GCGC スレッド数を設定。デフォルト値は JVM が動作しているプラットフォームによって異なる。
-XX:+UseCompressedOops Java ヒープサイズが 32GB 以下で最適化された 64bit パフォーマンスを得るために圧縮ポインタ (64bit ポインタの代わりに 32bit オフセットとして表現されるオブジェクトの参照) の使用を有効にする。
-XX:+AlwaysPreTouch JVM 初期化中に Java ヒープをプレ touch する。ヒープ上の全ページはアプリケーション実行中に段階的に行われるのではなく、初期化中の需要ゼロ状態で行われる。
-XX:AllocatePrefetchDistance= オブジェクト割り当てに対するプリフェッチの距離を設定。新しいオブジェクトの値が書き込まれようとしているメモリは、最後に割り当てられたオブジェクトのアドレスからこの距離 (バイト) でキャッシュにプリフェッチされる。各 Java スレッドは自分の割り当てポインタを持っている。デフォルト値は JVM が実行中のプラットフォームによって異なる。
-XX:InlineSmallCode= 事前コンパイルされたメソッドが、それから生成されたネイティブコードのサイズがこれより小さい場合にのみインライン展開する。デフォルト値は JVM が実行中のプラットフォームにより異なる。
-XX:MaxInlineSize=35 インライン化されるメソッドの最大バイトコードサイズ。
-XX:FreqInlineSize= 頻繁実行によってインライン化されるメソッドの最大バイトコードサイズ。デフォルト値は JVM が実行中のプラットフォームによって異なる。
-XX:LoopUnrollLimit= サーバコンパイラで、この値より小さい中間表現ノード数の場合にループ本体を展開する。サーバコンパイラで使用される制限は実際の値ではなくこの値の関数作用値。デフォルト値は JVM が実行されているプラットフォームにより異なる。
-XX:InitialTenuringThreshold=7 並列 Young GC の適用 GC サイジングで使用するための初期 Tenuring しきい値を設定。Tenuring しきい値は Old または Tenuring 世代に昇格する前に Young GC でオブジェクトが生存した回数。
-XX:MaxTenuringThreshold= 適応 GC サイジングで使用するための最大 Tenuring しきい値。現在の最大値は 15。デフォルト値は並列 GC で 15、CMS で 4。

メモ

原文に記載されていないオプションなど。

-XX:UseParNewGC 並列 Young GC を使用。並列 Mark-Seep GC と併用可能。
-XX:-PrintHeapAtGC GC 前後の詳細なヒープ情報を出力。
-XX:-PrintGCDateStamps GC を 2012-01-01T00:00:00.000+0900 形式のタイムスタンプで出力。
-XX:-CMSIncrementalMode (未調査)
-XX:MaxGCPauseMillis=nnn GC時に停止する最大ミリ秒(未調査)

Java SE 7 の言語拡張

Java SE 7 で導入された新しい言語拡張について簡単にまとめておきます。言語レベルの話ですので、ライブラリレベルの変更点はもっとたくさんありますが。

バイナリリテラル

int や long 値を表記する際に 0b11011 のような 2 進数表記の数値リテラルが可能になりました。バイナリ入出力でビットフラグやマスク、ビットパターンを記述する場合に便利ですね。

int x = 0b1010;
System.out.println(x);     // 10
数値リテラルのアンダースコア

数値リテラルの任意の位置にアンダースコア '_' を入れられるようになりました。これは 0x7FFF_FFFF のように可読性目的で使用するものです。とはいえ桁数の大きなリテラルを確認するケースに遭遇するのも年に何回かというところでしょうか。

long x = 9_223_372_036_854_775_807L;
System.out.printf("0x%X%n", x);    // 0x7FFFFFFFFFFFFFFF
switch 構文での文字列使用

switch 構文の値に文字列が使えるようになりました。

String x = "b";
switch(x){
case "a": System.out.println("A"); break;
case "b": System.out.println("B"); break;    // B
case "c": System.out.println("C"); break;
}

この文字列 switch は String#hashCode() を使用してジャンプテーブルを作成するため if-else 構文での文字列比較による分岐よりも効率よく動作します。またコンパイル時にジャンプテーブルを作成するため case に指定出来る文字列は定数リテラルのみです。

コンパイルされたバイトコードを読んで見ると、イメージ的には以下の様な動きとなっています。

String x = "b";
switch(x.hashCode()){
case 97 /* "a".hashCode() */: ...; break;
case 98 /* "b".hashCode() */: ...; break;
case 99 /* "c".hashCode() */: ...; break;
}
ジェネリック型推論のためのインスタンス生成の改善

総称型で定義された変数に new で代入する場合、右辺値の型パラメータが省略されていたら左辺値から推論するという機能。いわゆるダイヤモンド・オペレータという奴です。総称型の宣言文にありがちな長々としたステップを改善しコードの可視性を良くする目的で使用します。

Map<String,String> map = new HashMap<>();
map.put("a", "A");
map.put("b", "B");
System.out.println(map);    // {b=B, a=A}
try-with-resources 構文

ブロック終了時に自動的にリソースを開放するための構文。いわゆるローンパターンの完全形になったというところですか。java.lang.AutoCloseable または java.io.Closeable を実装したクラスを try のリソース宣言に一つ以上定義することで、try ブロックの終了時に必ず該当オブジェクトの close() が呼び出されます。

try(Reader in = new FileReader("foo.txt");
  Writer out = new FileWriter("bar.txt")){
  while(true){
    int ch = in.read();
    if(ch < 0) break;
    out.write(ch);
  }
}
複数種類の例外のキャッチ

try-catch 構文で複数の例外型をまとめて catch できるようになりました。セマンティクスとしては型だけ異なる catch ブロックが型の数だけ連なっているのと同じ意味です。

try {
  longPollingRead();
} catch(IOException | InterruptedException ex){
  ex.printStackTrace();
}

特定の例外のみ catch して共通処理を行いたい場合などに便利。catch した例外は throw する事も出来ます。

より包括的な型チェックでの再スロー

コンパイラが try-catch ブロック内で発生しうる例外の種類を判別し、その親クラス指定で catch した例外を個別の型で再 throw できるようになりました (前述の複数種類 catch と被っている気がしなくもないですが…)。

private static void read() throws IOException{ ... }
private static void poll() throws InterruptedException{ ... }
public static void foo() throws IOException, InterruptedException {
  try {
    poll();
    read();
  } catch(Exception ex){
    ex.printStackTrace();
    throw ex;
  }
}

Java 6 まで上記のコードでの foo() メソッドは throws Exception しか付けられませんでしたが、7 から try-catch 内で発生しうる例外のみを記述することが可能になります。

具体化されていない可変引数メソッドの警告

可変引数メソッドまたはコンストラクタにおいて具体化されていない (non-reifiable) フォーマルパラメータを使用した場合に宣言側で警告を出力するよう変更されました。以下 man ページから引用:

可変引数 (varargs) メソッド、特に非具象化可能引数を含むものの使用が安全でないことを警告します。次に例を示します。

public class ArrayBuilder {
  public static <T> void addToList (List<T> listArg, T... elements) {
    for (T x : elements) {
      listArg.add(x);
    }
  }
}

コンパイラは、varargs メソッドを検出すると、varargs の仮パラメータを配列に変換します。しかし、Java プログラミング言語では、パラメータ化された型の配列の作成を許可していません。メソッド ArrayBuilder.addToList では、コンパイラは varargs の仮パラメータ T... elements を仮パラメータ T elements(配列) に変換します。しかし、型消去のために、コンパイラは varargs の仮パラメータを Object elements に変換します。その結果、ヒープ汚染が発生する可能性があります。

このヒープ汚染に関する問題は "http://docs.oracle.com/javase/tutorial/java/generics/non-reifiable-varargs-type.html" 参照。

public static <T> String join(T... elem){
  StringBuilder buffer = new StringBuilder();
  for(T e: elem){ buffer.append(e.toString()); }
  return buffer.toString();
}

上記のコードを Java 7 でコンパイルすると以下の様な警告が出力されます。

torao@safran$ /usr/java/jdk1.7/bin/javac -Xlint:unchecked A.java
A.java:4: 警告:[unchecked] パラメータ化された可変引数型Tからのヒープ汚染の可能性があります
public static <T> String join(T... elem){
                                   ^
  Tが型変数の場合:
    メソッド <T>join(T...)で宣言されているT extends Object
警告1

英語のメッセージは "warning: [varargs] Possible heap pollution from parameterized vararg type T" です。この警告を抑止するために Java 7 コンパイラではオプション -Xlint:varargs とアノテーション @SafeVarargs, @SuppressWarnings({"unchecked", "varargs"}) が導入されています。

Java 7 で採用されなかった機能

リスト、マップの記述性改善

コレクションリテラルは個人的に入れて欲しかった。PythonRuby など Perl 系の言語で一般的な List や Map リテラルの簡略記法です。

List<String> list = ["A", "B", "C", "D"];    // Arrays.asList("A","B","C","D") と等価
Map<Integer,String> map = {0:"A", 1:"B", 2:"C", 3:"D"};

また煩わしい get/set/put の頻発を記述しやすくするために、リストやマップで添え字アクセスのシンタックスシュガーを付ける提案もあります。

List<String> list = Arrays.asList("A","B","C","D");
System.out.println(list[0]);
エルビス演算子

null 値チェックの記述を行うための簡略記法。Groovy でこのような書き方があるようです。Scala では Options を使うところでしょうか。名前の由来は ?: を横から見るとエルビスプレスリーに見えるためとか。構文展開が面倒だから見送られたのでしょうか。

String value = array?[0]?.getValue()?:"nothing";
// 以下と同じ
String value = null;
if(array != null && array[0] != null && array[0].getValue() != null){
  value = array[0].getValue();
} else {
  value = "nothing";
}
ラージ配列

現在の言語仕様で Integer.MAX_VALUE に制限されている配列の最大長をとっぱらおうというもの。 大量のログの集計など、一回実行できれば良いプログラムではわざわざ DB や B-Tree のページングなど作らなくても良いので楽かもしれません。ただそこまで大量のデータを扱うなら巨大配列クラスのような実装をライブラリレベルで提供した方が良いと個人的には思いますが。
この提案は Java VM レベルまで大改修が必要となるため見送られたのでしょうか。そんな気がします。

2011年の振り返り

FC2 でやっていた MOYO Laboratoryはてなダイアリーへ移転しました。とりあえずアカウントを作ったもののすぐに書けるネタもないので、初っ端から 2011 年の振り返りなどをしておこうかと思います。

1月

CMS 上で全文検索を行うために Solr の導入などをしていましたかね。

全文検索といえば殆どの Web アプリケーションで普遍的に必要となる機能です。昔は Perl を入れて Namazu でゴリゴリっとインデックスを作っておりました。2003 年頃には Lucene があったものの、日本語に関しては Perl 経由で Kakashi を呼ぶ必要があったりとかなりお粗末な出来栄えでした。Solr となってやっとミドルウェアとして使えるレベルのものになったかと感動。

オフは大道芸に舞台など。ヘブンアーティスト in 渋谷でシルブプレ、daichi というかなり芸術度の高いパフォーマンスを追っかけた。松たか子主演の舞台「十二夜」のチケットとれなかったー、と Twitter でつぶやいたらアコーディオン演奏者のプラノワ小春さんがわざわざ関係者枠で抑えてくれました。いやもう SNS の威力に驚きましたね。その後上野公園のカンカンバルカンで再び小春さんを見た後、国立科学博物館に行きました。

2月

同僚の家で料理などを食べました。
先月シルブプレのパフォーマンスで案内していた公演「エトランゼとエトランゼール」を見ました。

3月

3.11 時は恵比寿の弁当屋で被災。帰宅難民と称して 10km の道のりを道草しながら BLOG 更新しながら 7 時間かけて帰った。

MacBook を使っていたら突然パームレストあたりが盛り上がりバッテリーがお亡くなりになった。

  • 車検。
  • ゲルマーカーを買う。
4月
  • 同僚連中が実家に遊びに来る。
5月
6月
  • 宅鯖新設。Xeon 4Core で快適生活に入る。
  • Apache Thrift を触り始める。
  • 上野公園でプラノワを見る。
7月
8月
  • 第一回 Scala 会議参加。
  • クローラー実装のために JPA を使い始める。
  • 墨田ジャズフェスティバル。
9月
10月
  • ヘブンアーティスト in TOKYO。
  • 自宅サーバクローラーが動きそうな目処がついたので Web 側の機能を作り始める。Rails とか入れている。
  • Heroku にアカウント作成し公開の速さに驚愕するも、障害発生時の問題分析が全くできなくて放置となる。
  • 結局自宅サーバへの git push でデプロイまで行うシェルを作って幸せ生活に入る。
11月
  • 三茶DE大道芸。
  • Tokyo Dev Teatime。
  • 従兄弟の結婚式。
12月
まとめ

いろいろな意味で無駄な一年だった。

電子署名

電子署名 (Digital Signature) は文書に対する承認と、文書内容の保障を電子的に付け加えるための技術です。公開鍵を使用することにより他人によって行われた署名でない事、署名された時点から文書内容に変更がない事の 2 点を証明することができます。また電子署名法により 2001 年から電子商取引などにおける法的根拠としても使用できるようになりました。

電子署名を法的な保障付きで運用しようとすれば通常の公開鍵と同様のセキュリティが必要です。秘密鍵は署名者本人以外からアクセス可能であってはいけませんし、公開鍵は信頼の置ける第三者機関によって公開されなければいけません。

公開鍵が無効になれば署名の正当性を証明する手段もなくなります。CA が発行する公開鍵の証明書には数年程度の有効期限しかないため、現実世界の契約書のような長期間の管理が必要な文書には使用できません。

電子署名の原理

電子署名の実体は、文書から算出したハッシュ値公開鍵暗号で暗号化した数百バイト程度のバイナリデータです。ただし、暗号化に秘密鍵を使用する点が文書秘匿を目的とした場合と異なります。

署名の検証者は相手の公開鍵でその暗号が解除できるかどうかをもって対応する秘密鍵が使用された ─ つまりその所有者によって署名が行われたかどうかを検証することができます。そして署名から取り出したハッシュ値が文章のハッシュ値と一致していれば、その署名が行われてから文書に変更がないことを保障できます (公開鍵 暗号は非常に負荷の高い処理であるため、文書全体を暗号化する代わりにそれから算出 したハッシュ値を使用します)。

電子署名を信頼するには検証に使用する公開鍵が確実に相手のものであることを保障できなければいけません。また、秘密鍵を管理している側であればコンピュータの日時を巻き戻した上で改ざんした文書に再署名することも可能であることに注意する必要があります。

電子署名の作成と検証

Java で使用できる署名アルゴリズムこちらを参照。

アルゴリズムを指定して秘密鍵で初期化したSignature に対して、文書内容のバイナリデータで更新することで電子署名を算出することができます。生成される電子署名の実体は数百バイト程度のバイナリデータです。

Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(binary);
byte[] sign = signature.sign();

検証時は秘密鍵の代わりに公開鍵で初期化します。

Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(binary);
boolean valid = signature.verify(sign);

これにより秘密鍵にアクセスできる人間しか署名を行うことができず、署名の検証は誰でも行うことができるようになります。

ハッシュ関数


ハッシュ関数 (Hash Function) とは任意長のバイナリデータから数十〜数百ビット程度の固定長バイナリを算出する関数です。古くから CRC のようなアルゴリズム誤り検出の用途で使用されてきましたが、暗号などのセキュリティで使用されるハッシュ関数はような特徴を持ちます。

  • 同じバイナリデータに対して常に同じ値が算出される。
  • 異なるバイナリデータに対して同じ値が算出される (衝突) 確率が極めて低い。
  • 算出された値から元のバイナリデータの推測が極めて難しい。

最近ではマシンパフォーマンスの向上もあって、セキュリティの用途で使用できる強いハッシュ関数がバイナリデータの誤り検出や同一性検証 (インデックス付け) などにも使用されています。

ハッシュアルゴリズム

Java で使用できるハッシュアルゴリズムには Message Digest と SHA があります。誤り検出の用途であればツール等にも広く普及している MD5 (128bit)、セキュリティ的な用途では SHA-256 (256bit) 以上で十分と思われます (速度は大差ない)。

MD5
[128bit] Message Digest Algorithm 5: RSA と組み合わせて電子署名を行えるよう開発されたアルゴリズム。データの誤り検出からパスワードの保存まで広く使用されていますが、現在では (緊急性はないものの) いくつかの脆弱性が報告されているためセキュリティの用途では SHA を使用した方が良い。
SHA
[160,224,256,384,512bit] Secure Hash Algorithm: SHA-1 (160bit), SHA-224〜SHA-512 まで存在するアルゴリズムMD5 より攻撃に強いと言われ SSL, SSHIPSec などにも使用されている。

ハッシュ値の生成

ハッシュ値アルゴリズムを指定した MessageDigestにバイト配列を与えるだけで取得できます。

byte[] binary = "hello, world".getBytes();
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(binary);

対象のバイナリが大きい場合は update() メソッドを使用することで内容を何度かに分けて更新することができます。

InputStream in = // ...
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] buffer = new byte[1024];
while(true){
  int len = in.read(buffer);
  if(len < 0)	break;
  md.update(buffer, 0, len);
}
byte[] digest = md.digest();

DigestInputStream,DigestOutputStream クラスを使用することでストリームに入出力されるデータから透過的にハッシュ値を算出することができます。

InputStream in = // ...
MessageDigest md = MessageDigest.getInstance("SHA-256");
in = new DigestInputStream(in, md);
byte[] buffer = new byte[1024];
while(true){
  int len = in.read(buffer);
  if(len < 0)	break;
}
byte[] digest = md.digest();

アルゴリズムの確認

実行環境で使用できるハッシュアルゴリズムSecurity クラスで列挙することができます。
Java SE 6 でのデフォルトの実行結果は以下の通り。

SHA-256: 256bit
SHA-512: 512bit
SHA    : 160bit
SHA-384: 384bit
MD5    : 128bit
MD2    : 128bit