その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

sprint という Java の string formatter を書いた

sprint という Java 向けの String formatter を書いた.

Maven Central にも置いてある.

http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22net.moznion%22%20AND%20a%3A%22sprint%22

sprint は Sprint#ff() というメソッドを提供している.使い方としては以下の様な感じ.

final Sprint sprint = new Sprint();
System.out.Println(sprint.ff("Hello: {}!", "John")); // => Hello: John!

処理としては,template を parse して placeholder とそれ以外とを検出して,その検出した placeholder に変数を埋めつつ文字列を生成している.
そしてコードを読むと分かる通り,sprint は実際には StringBuilder の wrapper のようになっていて,適宜 StringBuilder#append() を呼びながら文字列を組み立てている.

工夫などとしては以下の通り.

簡易な template

sprint では簡単な template を利用する.例えば "{}: {}" という感じ.この template 中の {} は placeholder であり,sprint はこの placeholder に対して与えられたパラメータを埋めていく.メソッドの受け取る第一引数はこの template であり,以降の引数は placeholder に埋めるためのパラメータとして扱われる.この時,このパラメータは StringBuilder#append() に渡されるので,結果的に String.valueOf() を適用した結果が文字列中に埋められる.
これは Logback の文字列 template もこんな感じだったと思う.楽で良い.

template の parse 結果を使い回す.

文字列を format する度に template を parse していては効率的ではない.template に対する parse の結果は一意のはずで,記憶しておいて以降の処理で使いまわしたとしても矛盾は起きないはず.なので sprint では template の parse 結果を template 文字列と紐付けてインスタンス内に保持し,同じ template を使った二回目以降の format の際にはその保持しておいた結果を用いる.つまり,sprint では template の parse は一度だけ実行されるということになる.
なお,sprint ではこの parse した結果の構造を走査することで文字列を生成している.

こうした処理の削減によりパフォーマンスが向上している.ベンチマークの結果としては以下の様な感じ(jdk1.8.0_92 で実行.コードはここ).

f:id:moznion:20160605223844p:plain

小さい

コードの規模は小さく,外部に対する依存がないので組み込みやすい.
あと Java 7 でも動くので Android とかでも使えそう.


という感じ.
String formatter はかねがね作りたいと思っていたのでこの土日で作ってみた次第.
どうぞご利用ください.

git-reviewer 書いた

code review の reviewer 選出をする時,pull request の内容をざっと眺めてから「この部分だから XX さんかな」とか「あそこのコードは YY さんが詳しいだろう」とか,そういう感じで選ぶことが多くて,つまりは勘と経験で選びがちになってしまう.これについては常々いくばくかの危うさを感じていた.
そもそも,「reviewer として誰が最適か」という知識はプロジェクトに長く関わっている人でなければ知りにくいものであり,いわば属人的な知識のひとつだと思っている.プロジェクトからそういった長老的な人がいなくなってしまったら,最適な code review を実施できなくなってしまう可能性がある.


従って,やはり技術で解決ということになる.
Facebook が作っている mention-bot という GitHubbot として動作するやつがあって,これは pull request が送られてくるとその pull request について blame を実行して code reviewer の候補を出してくれるという気の利いた処理を自動で行ってくれる.

mention-bot は便利で,我々も使っているのだけれど若干の不満もある (おおよそ良いのだけれど……)

  • WIP の pull request だと,作業途中時点での reviewer が選出されてしまう.mention-bot は pull request が送られた時点での reviewer を選出してしまうので,WIP pull request との相性が悪い.
  • GitHub じゃないと動かない

後者は身も蓋もない話だけれど,前者については若干問題があるなーと思っていた.
任意の時点での reviewer 選出をもっと気軽にできれば良いのに,と.


というわけで,git-reviewer というスクリプトを書いた.手元で git のサブコマンドを実行することで reviewer 候補の選出が出来る.

使い方は至って簡単で,

$ git reviewer <into branch> <from branch>

としてやると,その brnach 間の diff について最適と思われる reviewer を選出してくれる (into branch を省略すると,current branch が into branch として扱われる).


仕組みとしては極めてシンプルで,

1. branch 間の diff を取る
2. diff が出た各ファイルについて,削除された行をかき集める (すなわち,diff の先頭に - が付いている行)
3. 削除された行 (つまり変更を入れられた行) のもともとの author を git blame により特定する
4. その author をかき集める

という処理を行い,頻出する author が reviewer 候補として選出される.
もしも diff に削除行が1つもない場合は,変更があったファイルの全行についてその author を集計し,その数が多い人を reviewer 候補として扱うようにしている.


このコマンドを手元で実行することで,手軽に reviewer 候補を知ることが出来て便利になった.めでたしめでたし.
何か「こうした方が良いのではないか」「おかしいのではないか」などがあったら教えて下さい.


[追記]
実行してみればお分かり頂けると思うのだけれど,git-reviewer の出力はとてもシンプルなものになっている.以下のような感じ.

moznion: 123
nozniom: 42
foobar: 2

これらは影響行数の降順として出力されるので,上に表示されればされるほど reviewer 力が高いという事になる.

もしも除外したい committer がいるならば,パイプで grep -v とかで除外すれば良いのかな〜とか思っていたのだけれど,確かに reviewer 側のオプションで食わせられるようにしても良いかもしれない.これが UNIX 哲学だ!! と頭ごなしに殴りつけても良いことはないのです.参考になりました.


以下はコミュニケーションの様子

Docker 使って golang で書いたツールの cross platform build をする

まず以下の様なシェルスクリプトを用意する.

#!/bin/bash

# ここで依存しているパッケージを go get する
# 例えば以下の様な感じ
# go get -v gopkg.in/yaml.v2
# go get -v gopkg.in/redis.v3

for GOOS in darwin linux; do
  for GOARCH in 386 amd64; do
    export GOOS
    export GOARCH
    go build -v -o bin/tool-$GOOS-$GOARCH main.go
  done
done

darwinlinux について,それぞれ i386amd64 アーキテクチャ向けのバイナリを作るようなスクリプト.main.go はビルド対象のファイル.成果物はカレントディレクトリの bin 以下に生成されるので,あらかじめ mkdir しておく必要がある (後述).

なお,GOOSGOARCH を export しておかないとうまく動かない.ドキュメントではこれが省略されていて少しハマった.

そんでもってこのスクリプトを呼び出す.以下は Makefile の例.

build:
	docker run --rm -v "$(PWD)":/go/src/github.com/moznion/tool -w /go/src/github.com/moznion/tool golang:1.6 bash build.sh

ここでは docker hub で公開されている golang のイメージを利用している.
https://hub.docker.com/_/golang/

-v オプションを使ってカレントディレクトリに docker image 内の go path が通っている然るべきディレクトリをマウントする.
でもって,-w オプションでマウントしたディレクトリをワーキングディレクトリに設定し,上記のシェルスクリプトを image 内で実行する.

と,ワーキングディレクトリ以下の bin に成果物が吐き出され,ワーキングディレクトリはカレントディレクトリにマウントされているので,つまり成果物はカレントディレクトリ以下の bin に出力されてハッピーという寸法!

なお,--rm オプションを付けておかないと実行する度にゴミ container がどんどん溜まっていくのでつけたほうが良い.

Ref;

実際のコードはここに書いてある感じのやつ


追記

cgo 使わない,なおかつ go 1.5 以上であれば make だけで完結するとのこと!

Docker 使って golang で書いたツールの cross platform build をする - その手の平は尻もつかめるさ

別にdockerもシェルスクリプトもいらなくて makeだけで完結しそうな予感/アンサーソングしといた http://lestrrat.ldblog.jp/archives/48673706.html

2016/05/24 06:27
b.hatena.ne.jp

なお,gox 及び goxc については go 1.4 以下の便利ツールという立ち位置とのこと.

resque_exporter 書いた

最近 Prometheus を使って各種メトリクスを取っていて,ふと resque の queue のステータス,つまり各 queue に 積まれている job の数を集計・可視化したくなったので,そのための exporter を書きました *1

実装としては,resque は <namespace>:queues という SET に全ての queue の名前を持っているので,SMEMBER を使って全 queue 名を取得してきて,
更にその各 queue 名に対応する <namespace>:queue:<queue_name> という LIST が queue そのものを表しているので,その LIST の長さを LLEN により取得することで各 queue の job 数を集計するという仕組みになっています (ここらへんがその実装).

なお,この exporter は resque 互換の job queue であれば問題なく動作するようになっていると思います (実際に我々は resque の java 実装である jesque でこれを使っています).


ところで golang で Prometheus の exporter を書くのは至って簡単で,

  1. prometheus/client_golang の Collector interface を実装する
  2. prometheus.MustRegister(exporter) と言う感じで Collector interface の実装を登録する
  3. http.Handle("/metrics", prometheus.Handler()) という感じで http 経由で export する口を作って serve

という感じでめいめい exporter を提供することが出来ます.シンプル!


なお補足ですが,色々な exporter の実装を見ていると scrapeFailures という counter を用意してあげて,そこに metrics 取得に失敗した回数を突っ込んでやるのがマナーっぽい感じでしたので,当 exporter でもそのようにしております.

*1:今までは fluentd + kibana という構成で似たようなことをやっていた

redis-script-manager 書いた

p5-Redis-ScriptJava 移植になります.
Redis を使ってて困った時にインターネットを徘徊してると @shogo82148 さんのブログにたどり着く事が多く,日頃お世話になってるわけですが,今回は Redisのトランザクション・スクリプト・ランキングを扱うPerlモジュールを公開しました - Shogo's Blog からの知見です.

上記の記事中には

EVALコマンドのドキュメントによると、 「EVALSHAで実行してみて NOSCRIPT No matching script で失敗したらEVALでやり直す」というのがおすすめらしいです。 EVALコマンドはSHA1ハッシュの登録も行ってくれるので、初回 NOSCRIPT になっても次回からはEVALSHAが成功します。

そんなに複雑なことではないのですが、毎回書くのも大変なのでモジュールとして切り出したのが Redis::Script です。 以下のようにスクリプトオブジェクトを作っておいて、パラメータを渡して実行します。

とあり,まさにこの度そういった utility 的なものを毎度毎度書くのもだるくなったので,ここは一丁 library 化しようということで今回 Java に移植した次第.
Java でやるなら,特定の script について SCRIPT LOAD を一度だけ行なうような singleton class を用意してごにょごにょやるという方法も考えましたが,基本的な実装は p5-Redis-Script を踏襲しています *1

redis-script-manager としては Jedislettuce の2つの Redis Client に対応しています.

ひとまずこれだけ対応しておけば自分で使う分は大丈夫だろう,という判断からこの2つに対応しました.
もしも他の Redis Client のサポートが必要になったら,redis-script-manager-core の抽象クラスである ScriptManager を継承して実装すれば同様の挙動を実現できるので,その時が来たら考えるぞ! という感じです.


ところでこういう細かい library を書いて使っていくのはあまり Java の文化っぽくない感じがあり,その背景には依存する jar が増えれば増える程地獄と化してきて厳しい,などといった状況があることと思います.確かにそうだ.
しかしだからといってコードをいちいちコピペして使い回すとかはあまりしたくないし,やっぱりかゆい所に手が届くパーツがあれば便利やん? みたいな感覚があるので,とりあえず外に出して使ってみるのが良いのではないかと思っているところです.

*1:むしろ ScriptManager ごと singleton にするという戦略はありかもしれない

Server::Starter + Java 環境下で JMX による監視を有効にしていると graceful restart 時に不具合が出ちゃって困るんですけど〜って時

割とニッチな話題ではありますが……


Server::Starter を使ってプロセスを立ち上げると graceful restart を簡単に実現できるなど便利な点が多く,LL 時代はこれでやっていっていたわけですが,残念なことに Java からその Server::Starter テクノロジを利用するのは長らく不可能なものと思われてきました.しかし昨年2015年の中頃に Java からでも Server::Starter の利用が可能であることが id:tokuhirom 氏により発見された (+ Server::Starter にパッチが送られた) ため,Java からでもお手軽に Server::Starter を用いた graceful restart が出来るようになりました.
Server::Starter については参考になる記事がインターネット上にたくさんありますから適宜検索してもらうとして,Java から Server::Starter を利用する術については以下を参照してください.


さて本題ですが,タイトルが長いのでわかりやすく分割して書きますと,

  • Server::Starter を使って Java プロセスを立ち上げていて
  • JMX による 監視を有効にしていて
  • Graceful Restart を行った時

に,JMX が port を食い合ってしまうために上手く restart できないという問題についての話です.

例えば,以下のようにして Server::Starter + Java + jmx を起動してから,

$ java \
    ...
    -Dcom.sun.management.jmxremote \
    -Dcom.sun.management.jmxremote.port=5555 \
    -Dcom.sun.management.jmxremote.rmi.port=5555 \
    Main

restart を行うと,瞬間的に2つの Java のプロセスが立ち上がるため,JMX の connector server が port を食い合って (新プロセスの方が "Address already in use" を吐く) 新しいプロセスを上手く立ち上げることがきなくなります.こうした場合,connector server は立ち上がりませんし,手法によっては古いプロセス・新しいプロセスの両方とも終了せずに無限に起動し続けるといった,言うなればデッドロックのような状況に陥るかもしれません.

というわけでどうするかというと,コマンドラインオプションを指定する代わりに手で connector server を立ち上げるコードを書いて,daemon の起動時に立ち上げてやります.

public void startJMXConnectorServer() throws IOException {
    final int port = 5555;
    LocateRegistry.createRegistry(port);
    final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi");
    final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
    JMXConnectorServerFactory.newJMXConnectorServer(url, null, mBeanServer).start();
}

こんな感じのものを書いて,立ち上げのタイミングで startJMXConnectorServer() を呼び出してやると,コマンドラインオプションで指定した時と同様の効果が得られます.
が,しかしこれで上手くいくかと思いきや,このままだとコマンドラインオプションを使った手法と同様で上手く resstart することが出来ません (依然 "Address already in use" が出る).
ので,苦肉の策でこうしてやる.

public void startJMXConnectorServer() {
    final int port = 5555;
    boolean isJMXLaunched = false;
    for (int i = 0; i < 100; i++) {
        try {
            LocateRegistry.createRegistry(port);
            final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi");
            final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
            JMXConnectorServerFactory.newJMXConnectorServer(url, null, mBeanServer).start();
        } catch (IOException e) {
            // Might be "address already in use" error, retry
            try {
                Thread.sleep(300);
            } catch (InterruptedException ie) {
                log.warn("Interrupted");
            }
            continue;
        }
        isJMXLaunched = true;
        break;
    }
    if (!isJMXLaunched) {
        throw new RuntimeException("Failed to start JMX connector server");
    }
    log.info("JMX connector server started");
}

スピンロックのような感じで port が空くまで待って,port が空いた,すなわち古いプロセスが終了したら connector server を立ち上げるという感じ.これでひとまず動くっちゃ動く.良かった良かった.

しかしこの方法だと起動のタイミングによっては「古いプロセスの破棄」と「新しいプロセスの完全な立ち上がり」との狭間に落ちて,リクエストを取りこぼしてしまう可能性があるので,実装に (つまり startJMXConnectorServer() を呼び出すタイミングに) 気を使う必要がありそうです.
あるいは Web Application のようなものであればこんな方法を使わずに,起動してから最初にリクエストが来た瞬間に connector server を立ち上げてやる,というような方法でも良いかもしれませんね.

こちらからは以上です.

Web Application Server を動かす時の Java8 起動オプションのメモ

一般的な Web Application Server *1 を Java8 で動かすにあたって,最近有効にしている起動オプションについてメモ.
何か間違っていたり,あるいは「こっちの方が良い」みたいなのがあれば教えて下さい.

-server

server mode で起動させる (指定しないと client mode になる可能性がある,マシンスペックによってスイッチする?).

-Djava.net.preferIPv4Stack=true

If IPv6 is available on the operating system the underlying native socket will be an IPv6 socket. This allows Java(tm) applications to connect too, and accept connections from, both IPv4 and IPv6 hosts.
If an application has a preference to only use IPv4 sockets then this property can be set to true. The implication is that the application will not be able to communicate with IPv6 hosts.
https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html

IPv6 が使える OS では IPv6IPv4 両方のホストと接続できるが,true にすることで IPv4 ソケットだけを使うようにする.

-Dnetworkaddress.cache.ttl=0
networkaddress.cache.ttl=0

Specified in java.security to indicate the caching policy for successful name lookups from the name service.. The value is specified as integer to indicate the number of seconds to cache the successful lookup.
A value of -1 indicates "cache forever". The default behavior is to cache forever when a security manager is installed, and to cache for an implementation specific period of time, when a security manager is not installed.
https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html

デフォルトでは DNS の成功結果を未来永劫キャッシュするようになっていてアレなので,0を設定することでキャッシュしないようにする.

[追記]
id:astjさんに教えてもらったんですが,このオプションはセキュリティポリシーのため -D では有効になりません.

セキュリティーポリシーの一部であるため、-D オプションや System.setProperty() API では設定されません。その代わり、これらのプロパティーは JRE のセキュリティーポリシーファイル lib/security/java.security で設定されます。
https://docs.oracle.com/javase/jp/7/api/java/net/doc-files/net-properties.html

すみませんすみません.

-Dnetworkaddress.cache.negative.ttl=0
networkaddress.cache.negative.ttl=0

Specified in java.security to indicate the caching policy for un-successful name lookups from the name service.. The value is specified as integer to indicate the number of seconds to cache the failure for un-successful lookups.
A value of 0 indicates "never cache". A value of -1 indicates "cache forever".
https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html

デフォルトでは DNS の失敗結果を 10 秒キャッシュするようになっている.0をセットすることでキャッシュしないようにする.

[追記]
上と同じ理由で-Dでは有効にならないのでセキュリティポリシーで設定してください.すみませんすみません.

-XX:OnOutOfMemoryError="kill -9 %p"

OutOfMemory が発生した時に実行するコマンドを設定できる.OOM が起きたら確実に自殺してもらう必要があるので kill を発行.事と次第によっては自動でアプリケーションを再起動するようにしても良いかもしれない.
Ref: OutOfMemoryErrorが発生したときにきちんとJavaプロセスを殺す - nekop's blog

[追記]
@さんに教えていただきましたが,Java8u92から追加された-XX:+ExitOnOutOfMemoryErrorを利用したほうが良さそうです (XX:OnOutOfMemoryErrorに指定したコマンドが間違っていてうっかり死なない,みたいなミスを回避できる).

-XX:+OptimizeStringConcat

Optimize String concatenation operations where possible
http://www.oracle.com/technetwork/articles/java/vmoptions-jsp-140102.html

文字列連結が最適化されるとのこと (Java 8 ではデフォルト true っぽい).

-verbose:gc
-Xloggc:/var/log/gc/gc.log.`date +%Y%m%d%H%M%S`

gc log を吐くようにする.またそれをどこに吐くかの設定.
log ファイル名のうしろに日付情報を付与することでlogが上書きされるのを防いでいる.

-XX:+UseGCLogFileRotation

gc log を1つのファイルに延々吐き続けるのではなく,ログローテーションを有効にする.
後述の XX:NumberOfGCLogFiles と XX:GCLogFileSize に関係がある

-XX:NumberOfGCLogFiles=5

ログローテーションさせるときに,最大何個のログファイルでローテーションさせるかの設定.数字 (ファイル個数) は任意.

-XX:GCLogFileSize=1024MB

gc log 1個あたりの最大容量 .容量は任意.

-Xms 2G

ヒープ領域の初期値.値については各アプリケーションごとにチューニングの余地がある.

-Xmx 2G

ヒープ領域の最大値.値については各アプリケーションごとにチューニングの余地がある.

-Xmn 512m

New 世代領域サイズ.値については各アプリケーションごとにチューニングの余地がある.

-Xss 256K

スレッドスタックサイズ.値については各アプリケーションごとにチューニングの余地がある.

-XX:MaxMetaspaceSize=128M

!!! これめっちゃ重要 !!!
Java 8以降だと,かつての「-XX:MaxPermSize」の代わりに設定してやる必要がある.
Permanent 領域は最大容量が有限だったのに対し,Java 8 以降の Metaspace 領域はメモリの許す限りモリモリ消費できてしまうので,最大容量を指定してキャップすることで安全側に倒してやる必要がある.
Ref: http://equj65.net/tech/java8hotspot/

-XX:+UseConcMarkSweepGC

Use concurrent mark-sweep collection for the old generation

CMS GC を使う.
Java のバージョンによってデフォルトのGCが変わりそうなので,設定したほうが安全な感じがした.

-XX:+CMSParallelRemarkEnabled

メジャーGCのRemarkフェイズをマルチスレッドで実行
(「動作させるマシンのCPUが2個以上かつ物理メモリが2Gbytes以上の場合には、自動設定される」とあるが念のため: http://www.atmarkit.co.jp/ait/articles/0704/24/news136_2.html)

-XX:+UseParNewGC

マイナーGCをマルチスレッドで実行
(「動作させるマシンのCPUが2個以上かつ物理メモリが2Gbytes以上の場合には、自動設定される」とあるが念のため: http://www.atmarkit.co.jp/ait/articles/0704/24/news136_2.html)

-XX:+UseCMSInitiatingOccupancyOnly

CMSが開始されるトリガーは二つある。ひとつはOld領域の利用率がCMSInitiatingOccupancyFraction に到達した場合。もうひとつは今CMS走らせないと先にヒープ埋まっちゃうよね、という統計判断を元にしたトリガー。UseCMSInitiatingOccupancyOnlyを付与すると後者のトリガーが無効になる。
http://nekop.hatenablog.com/entry/20140327/1395886237

というわけで,統計判断を無効に.

-XX:CMSInitiatingOccupancyFraction=75

XX:+UseCMSInitiatingOccupancyOnly と関連.

これらのデフォルトはMinHeapFreeRatio=40、CMSTriggerRatio=80なので、CMSInitiatingOccupancyFractionのデフォルト値は92になる。つまり、Old領域が92%になったときに最初のCMS GCが行われる。オブジェクトアロケーションが激しいようなアプリケーションでは92%だと手遅れになることがあるのでCMSInitiatingOccupancyFractionは下げたほうが良い。70とか。
http://nekop.hatenablog.com/entry/20140327/1395886237

[追記]

XX:CMSInitiatingPermOccupancyFraction=percent
Sets the percentage of the permanent generation occupancy (0 to 100) at which to start a GC. This option was deprecated in JDK 8 with no replacement.
http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

JDK8 以降は非推奨とのこと……JDK 9と出会ったらまた考えましょう

[追記]
@さんに教えていただきましたが,これはオプション自体が非推奨なわけではなく,JDK 9ではCMS GC自体が非推奨のためこのような記述になっているとのこと.

-XX:+ScavengeBeforeFullGC

FullGC 走らせる前に young の GC を走らせる

-XX:+CMSScavengeBeforeRemark

If turned on, it will cause a minor collection to occur just before the remark. That's good because it will reduce the remark pause. That's bad because there is a minor collection pause followed immediately by the remark pause which looks like 1 big fat pause.
https://blogs.oracle.com/jonthecollector/entry/did_you_know

CMS の Remark 処理の前にマイナーの scavenge をするということらしい.

-XX:+TieredCompilation

階層型コンパイルを有効にする.有効にすると,コンパイル済コードの実行中に JVM が最適化のための統計情報を収集可能にする.

この改良の有効性はそれほど高くはないと思われる。サーバのスタートアップ時の処理時間は,デスクトップやアプレットベースのアプリケーションほど重要ではないからだ。ただし非常にダイナミックなシステムにおいて各ノードがすばやく立ち上がる必要のある場合などには,この変更でウォームアップ時間を短縮できるかも知れない。
http://www.infoq.com/jp/news/2011/11/java7-u1

とのことだが……

-XX:+UseCompressedOops

Enables the use of compressed pointers (object references represented as 32 bit offsets instead of 64-bit pointers) for optimized 64-bit performance with Java heap sizes less than 32gb.

XX:+UseCompressedOops オプションを使用すると、Java オブジェクトヒープのサイズが 32 ギガバイト未満の場合に、64 ビット JRE のパフォーマンスを向上させることができます。この場合、HotSpot はオブジェクト参照を 32 ビットに圧縮して、処理する必要のあるデータの量を減らします。

Ref: http://itdoc.hitachi.co.jp/manuals/link/has_v101000/0342020D/0766.HTM
Ref: http://www.oracle.com/technetwork/jp/articles/java/compressedoops-427542-ja.html
Ref: http://d.hatena.ne.jp/quitada/20120130/p1

-XX:+PrintGCDetails

gc log に詳細な情報を吐き出すようにする.GC algorithm や JVM のバージョンによって追加される情報は変わってくるようだが,とりあえず GCスループットの情報が出たりする.

-XX:+PrintGCApplicationStoppedTime

Stop The World が起きた時,stop した時間を gc log に書き出すようにする.

-XX:+PrintGCDateStamps

gc log の各行の先頭に日時のスタンプを付与する (PrintGCTimeStamps にすると,JVM が起動してからの秒数が先頭に付与されるようになる.ヒトが読みにくい気がするので Date に)

-XX:+PrintTenuringDistribution

young領域からold領域への昇格(昇格?)に関するログを出力するためのオプション.

-XX:+HeapDumpOnOutOfMemoryError

OOM Error が起きた時に Heap Dump を吐き出すようにする.

雑感

Xms,Xmx,Xmn 及び Xss は割と勘と経験で設定している感じがあるので,改善の余地が残されている気がする.
以下の記事なんかを参考にするのが良いのか;

JVMのチューニング - ITエンジニアとして生きる
[調査]JVMのスタックサイズについて - Akira's Tech Notes
ガベージコレクタの仕組みを理解する:チューニングのためのJava VM講座(後編) - @IT

*1:一般的の定義が曖昧