2015年11月16日月曜日

ASMのドキュメントを読む(Core API - Methods - Structure)

Javaバイトコード操作のためのライブラリであるASMのガイドのメモの第1弾。
最新版はバージョン5系だが、ガイドは4のものしかない。
http://download.forge.objectweb.org/asm/asm4-guide.pdf

今回はASMまでは言及せずに、JVMの実行モデルについての概念の整理。
  • 各スレッドは実行スタックを持っている(ヒープはスレッドが共有する)
  • 実行スタックには複数のフレームから成る(フレーム1つが、1回のメソッド呼び出しに相当する)
  • フレームはローカル変数部とオペランドスタックから成る(ローカル変数にはインデックスが付いたリスト、オペランドスタックはバイトコード命令のためのスタックである。
  • ちょっと複雑だが、実行スタックの中にオペランドスタックが入っていることになる。
  • バイトコード命令は、opcodeとopcodeごとに決まった数の引数から成る(opcodeは命令の内容を識別するコードで1バイトで表現される。だから「バイトコード」と呼ぶ。)
  • オペランドスタックにはバイトコード命令そのものと、そのバイトコード命令で使うデータも格納する(つまりデータの格納場所としてはローカル変数部とオペランドスタックの2箇所がある)
  • バイトコード命令には、ローカル変数部とオペランドスタックの間でデータを移動させる命令と、オペランドスタックの中だけで操作する命令の2種類がある(あらゆるメソッドが1フレームという単純なルールで表現できるのはちょっと不思議な感じがする)
Javaはオブジェクト指向言語なので、メソッド呼び出し=メッセージ・パッシングである。あるインスタンスメソッドを呼び出す場合、ローカル変数部にはthisと引数が格納される。実際は、ローカル変数はthisのインスタンスはヒープに存在してローカル変数部にはポインタが入っているだけ。longやdouble型の値は、2スロット分を使って格納する(つまり1スロットは4バイト?)。

ASMのガイドにはいろんな例が載っている。例えばpkg.Beanクラスのfというint型フィールドのgetterは以下のようになる。
ALOAD 0・・・ローカル変数部に格納されているthisをオペランドスタックに積む。
GETFIELD pkg/Bean f I・・・pkg.Beanクラスのオブジェクトのfというフィールドのint型の値を取得してオペランドスタックに積む。thisはpopされる。
IRETURN・・・オペランドスタック上のint型の値をpopしてメソッドの戻り値として返す。

fという名前は定数プールから名前解決される。ここを見るとバイトコード命令の実行前後でオペランドスタックの状態がどのように遷移するかがわかりやすい。

次回からASMでバイトコードを扱う方法に入っていく。

2015年11月15日日曜日

javax.annotation.processing

アノテーションを使って処理するための機能が入っているのがjavax.annotation.processing。アノテーション処理をラウンドという単位に分けて段階的に処理を行う。各ラウンドではあるプロセッサーに定義された処理を行う。プロセッサーはアノテーションをもとにJavaソースコードやXMLファイルなどを生成する。ラウンドというのは生成されたJavaソースコードをさらに別のプロセッサーにかけることを意図したもの。普通はjavacの実行時にプロセッサーを実行する。
https://docs.oracle.com/javase/jp/8/api/index.html?javax/annotation/processing/Processor.html

java.lang.instrument

JVMにはインストゥルメンテーションという機能があって、実行中にバイトコードを書き換えることができる(元々は実行中のプログラムの計測のためのものらしい)。
https://docs.oracle.com/javase/jp/8/api/java/lang/instrument/package-summary.html

 プログラマーがバイトコードを書き換えるためには、エージェントというものを作る。エージェントを書き換え対象のJVMに適用するには2通りの方法がある。
  1. JVMの実行時に-javaagentオプションによってエージェントJARを指定する。-javaagentは複数指定できる。
  2. すでに起動済みのJVMに対してエージェントを適用する場合は、別JVMとしてエージェントを起動して起動済みのJVMにアタッチする。jconsoleがこちらのパターン。Attach APIというものを使うようだが、これは開発ベンダー依存APIなので注意。
バイトコードを書き換えることができるので、普通のプロダクトではできないようなある意味、黒魔術っぽいことができるようになる。このインストゥルメンテーションを使っている有名なプロダクトを調べてみた(ドキュメントで調べただけで実際には1つも試してはいない)。
  • JRebel・・・サーバー上にデプロイするアプリケーションのように長時間起動するアプリケーションを開発中にソースコードを書き換えた時にIDEがコンパイルしたバイトコードを使ってサーバーが使っているバイトコードを置き換えることで、リアルタイムに変更を反映できる。いわゆるホットスワッピングとかホットコード置換とかホットデプロイと呼ばれるもの。デバッガーがコードを変更するのと似ているが、適用できる範囲が違うらしいSpring-Loadedも似ている。販売元による比較がこちらにある。Seaser(S2Container)はDIコンテナだからエージェントを使ったアプローチではないけど実現しようとしていることは似ている。
  • NewRelic・・・JVMに限らずソフトウェアの監視を行うサービスだが、JVMアプリケーションを監視する場合は、-javaagentで指定する。ログを監視サーバーに送るのかな。
  • Lombok・・・ お決まりの「ボイラープレート」コード削減することができる。EclipseなどのIDEに導入するときに-javaagentで指定する。エージェントだけでなくjavax.annotation.processingも使用して実現している。
  • JMockit・・・テスト時のモックを作るためのツール。
  • JaCoCo・・・テスト時のカバレッジ測定ツール。
  • AspectJ・・・アスペクト指向言語、ツール。
インストゥルメンテーションを使うと色々面白いことができるけど、リスクも多い。考慮点は以下の通り。
  1. 変更されたバイトコードそのものを見ることはできないから、ソースコードに書いた通りに動くことを求めているシチュエーションでは使うべきではない。つまり挙動そのものを書き換えないユースケースのみで使う方が安全。Lombokにはdelombokという機能で書き換えたソースコードを生成できる。NewRelicは性能監視しかしないので、副作用は限定的なはず。他のプロダクトは開発時やテスト時に使うのでほとんどの場合、実運用では使わない。AspectJは実運用でも使えてしまうので注意が必要。
  2. 複数のインストゥルメンテーション製品を使うとちゃんと動かないことがある。しかもエラーがわかりにくく、はまりやすい。
  3. リフレクションを使うよりは速い
  4. 学習コストが高い。バイトコードを操作する機能を作る時は、ASM、BCEL、Javassistなどを使うがどれも簡単ではない。例えばASMを使う場合はバイトコードとJVMの動作の仕組みを理解する必要がある。

2013年1月14日月曜日

MacにインストールされているJava

(2015/11/22追記)
ターミナルからjavaコマンドやjavacコマンドを実行する場合は、JAVA_HOME環境変数でJavaのバージョンを指定します。bash, zshの場合は.bashrcや.zshrcに記載してsource .bashrcなどで反映させます。以下の指定では、1.8系で最新のものが利用されます。
export JAVA_HOME=`/System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/java_home -v "1.8"`

とりあえずインストールされている最新版を使いたいという時は、-vのバージョン指定が不要です。
export JAVA_HOME=`/System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/java_home `

あまり用途はないと思いますが、細かいバージョン指定もできます。
export JAVA_HOME=`/System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/java_home -v "1.8.0_65"`

「システム環境設定」はJavaアプレットに関する設定なので、以下の手順は有効ではないですが、残しておきます。

########################################################
(以下は2013/1/14記載。ターミナルから実行する)
前回の続きです。

インストールされている1つのJDKの中のjavacとjavaが使えているのかよくわからないので、とりあえず、「システム環境設定」から「Java」を選んで確認してみます。

すると、セキュリティ・アップデートを促されます。

アップデートが終わるとJava 7になったはずなんですが、「ターミナル」上では1.6のままです。インストールされたパスが異なるようです。

Mac:~$ java -version
java version "1.6.0_37"
Java(TM) SE Runtime Environment (build 1.6.0_37-b06-434-11M3909)
Java HotSpot(TM) 64-Bit Server VM (build 20.12-b01-434, mixed mode)

Mac:~$ which java
/usr/bin/java

Mac:~$ ls -l /usr/bin/java
lrwxr-xr-x  1 root  wheel  74 10 20 02:05 /usr/bin/java@ -> /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java

おそらくJDK 7はインストールされていないので、せっかくなので最新化します。
インストール手順はこちらです。バージョンを確認してみると、javacもjavaも1.7.0_10になってました。またhelpも日本語になりました。
他のバージョンのJDKを使いたい場合は、/usr/libexec/java_homeコマンドで変更可能パスを取得するか、同じコマンドで直接実行できるみたいです。

2013年1月4日金曜日

Safariのブックマークをソートする

Safariにはなぜかブックマークをソートする機能がありません。お正月休みで暇を持て余しているので、ソートツールを作ってみます(きっとどこかにすでに存在するんでしょうけど)。勉強を兼ねて初挑戦のRubyで作ってみます。

ソートするといってもどこから手を付けていいかと思い調べたところ、Safariにはブックマークの書き出しと、読み込みの機能があります。書き出されるのはNETSCAPE Bookmark formatという形式のファイルです。

<!DOCTYPE NETSCAPE-Bookmark-file-1>
<HTML>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<Title>ブックマーク</Title>
<H1>ブックマーク</H1>
<DT><H3 FOLDED>ブックマークバー</H3>
<DL><p>
<DT><A HREF="http://www.blogger.com/home">Blogger</A>
<DT><A HREF="http://www.yahoo.co.jp/">Yahoo!</A>
</DL><p>
以下省略

一旦NETSCAPE Bookmark format形式で書きだしておいて、Rubyでソート後、Safariに読みこませればソート完了という計画です。これならRuby初心者には程よい練習問題になりそうです。

まず、ファイル形式を眺めてみると、HTMLとは異なり<HEAD>タグや<BODY>タグは無く、他のタグがきちんと閉じられていなかったりします。またこれはSafari限定だとおもいますが、<H1>ブックマーク</H1>の下には、<DT><H3 FOLDED>ブックマークバー</H3>、<DT><H3 FOLDED>ブックマークメニュー</H3>、<H1>ブックマーク</H1>とそれぞれの階層にブックマークの実態が<DT>タグで記述されています。私の場合、以下の様な構造でした。
  • ブックマーク
    • ブックマークバー
      • ブックマーク1
    • ブックマークメニュー
      • フォルダ1
        • ブックマーク2
      • フォルダ2
        • ブックマーク3
      • ブックマーク4
    • ブックマーク5
一般的な構造がどうなっているのかまでは調べると大変なので、とりあえず自分のブックマークだけソートできることを目標にします。

はじめはREXMLというXML操作用ライブラリを使ってみようと思いましたが、やってみると正しいXMLフォーマットではないために色々面倒な事に。単純にタグが閉じられていないのと、親子関係が解析しづらいために、ベタに1行ずつ読み込んでいくことにしました。

完成したのはこちら。
main.rb
bookmark.rb
使用方法はこちら。標準出力にソート結果が出力されます。
ruby main.rb input.html
いつもはJava、Eclipseを使用しており、Rubyは初めてで作成に結構時間かかりましたが、感想です。

  • コード作成にはHomebrewでインストールした素のVimを使用しましたが、Eclipseを使い慣れているので機能的に物足りないです。.vimrcの設定等でどこまで機能追加できるか調べてみたいです。
  • デバッグ実行を行う際にruby -r debugを使用しました。今回はそうでもなかったですが、コード量が多くなると厳しいです。世の中一般ではどういう環境で開発しているんでしょうか。
  • 動的型付け言語で変数に型が付いていないのでなんでも代入できてしまうというのが、まったく慣れないです。
  • 他にもAPIが分からない等いろいろありますが、慣れれば少ないコーディング量で機能を実現できるんでしょう。まだまだ先は長いです。

2013年1月3日木曜日

映画「ドライヴ」を見ました

お正月で実家に帰省中に暇すぎてiTunes Storeを眺めていたら、前から気になっていた映画「ドライヴ」が出ていたので見てみました。

主人公のドライバー(名無し)と隣人家族とのふわふわ・ほろ苦なお付き合いシーンから暴力シーンに変わる振り幅が大きく、久しぶりに手に汗握りながら見てました。ドライバーが恋してしまう女性とその子供と一緒にいる場面では青春映画かと思うような優しい音楽が流れ、かと思えば暴力シーンでは前触れ無く冷徹でゴアな表現。

女性と子供も決して安全ではなく、一瞬ですべてを壊されてしまうかもしれないというドキドキ感が満載でした。主演のライアン・ゴズリングが好きになりました。あと冒頭のドライブシーンがカッコ良かったので、もう少し見たかったです。

2013年1月2日水曜日

Eclipseでビルドした場合に使用されるコンパイラ

Eclipseでビルドすると、Eclipse内部のJava development tools(JDT)に含まれるコンパイラによってビルドされる(参考)。
JDT Core is the Java infrastructure of the Java IDE. It includes:
An incremental Java compiler. Implemented as an Eclipse builder, it is based on technology evolved from VisualAge for Java compiler. In particular, it allows to run and debug code which still contains unresolved errors.
なので、javacコマンドを使ってビルドしたものとは厳密には異なる。ターミナル上でJDTに含まれるビルダを使ってビルドすることもできる。例えば、src_dirディレクトリに存在するjavaファイルをEclipse3.8に含まれるJDTを使って、Java1.6準拠でビルドしたい場合は、以下のコマンドを実行。
$ java -jar org.eclipse.jdt.core_3.8.2.v20120814-155456.jar -1.6 src_dir
ヘルプは以下のコマンドで表示すると他のオプションも確認できる。
$ java -jar org.eclipse.jdt.core_3.8.2.v20120814-155456.jar

DockerでMQ環境を作る

DockerHubの ibmcom/mq を使う。ファイルシステムとしてtmpfsは使えないので これ を参考に/mnt/sda1に置き換える。 brew install docker docker-machine docker-machine create --drive...