[JAVA] バイトコード変換を使うべきか否か?

原文
http://www.javalobby.org/java/forums/t101600.html

筆者
Daniel Spiewak*1


最近のGavin King*2との議論が私にバイトコード変換*3に関してや、その利点と欠点について考えるきっかけとなった。私は常にバイトコード変換の使用を回避してきた。なぜならばそれに含まれるリスクが得られる利点に比べとても危険だと常に感じてきたからだ。これは本当に正しいアプローチなのだろうか?


私にとって、それは1つの単純な事実にまとめられる。バイトコード変換はJavaコードの挙動をその仕様に反して実行時に変えてしまう。まさにそれ自身の定義により、バイトコード変換はJava標準仕様では許可されないことを(バイトコードで行える範囲で)行わせてしまう。不幸なことにこれがデバッグをとても難しくする可能性がある。そしてとても妙なエラーに導いてしまうこともある。例として、もし自動生成されたセクションに問題があった場合に、JVMは理解するのが難しい変なエラーを吐いてしまう。最低でも自動生成されたコードはどの特定したソースコードとも関連しない。結果として問題を解決することは普通に考えられるほど単純ではなくなるだろう。


さらに悪いことに、多くのフレームワーク開発者はバイトコード変換ライブラリをフレームワークの主要な機能を扱うために利用してしまっている。例としてHibernateはCGLIB*4を遅延ロードを実現するために使用している。Springはバイトコード変換をAOPに用いている。(実際に全てのAOPフレームワークがこのテクニックを用いている)
だからこれらのライブラリにて提供されるすごい機能の類を使うことによって、私たちは潜在的悪影響や妙なエラーにはまっているのだ。


こんなハッキーな技術を使うことが、目的を達成するために真にベストプラクティスなのだろうか?
バイトコード変換は本当に有効なツールなのだろうか?

Jess Sightler wrote:

君の質問の1つが回答する気にさせた。もちろんいわゆるハッキーなテクニックを使うことは望ましくない。しかしバイトコード変換はハッキーなんかじゃない。

確かにリスクは付き物だ。だけど全く同じリスクがJavaソースからバイトコードに変換するコンパイラを使用することにもある。Javaコンパイラのバグを見たことあるかい?JITは?ECJ*5のバグは凶暴だし、JITのバグのいくつかも同じような問題を抱えている。

結論としてこういうところにあるバギーなコードはとても悲惨でつきあうのが難しい。

幸運なことにちょっとしたことが役に立つんだけど、デザインさえ良ければ、テストが広範囲にかつ十分に行うことができる。

君がなぜバイトコード変換が標準仕様に反すると言うのかわからない。別にJythonコードをJavaバイトコードコンパイルするのと同じように標準仕様に反したりしない。単に違うやりかたで行ってるだけだ。

Eugene Kuleshov DeveloperZone Top 100 wrote:

Daniel, 君はまず自分の宿題をするべきだった。Springフレームワークバイトコード変換をAOPの機能性のために利用していない。AOP実装は完全にProxyベースで行われている。しかしSpringは実行時にバイトコードを自動生成することもできる。java.lang.reflect.Proxyでは扱えないProxyクラスのためにだ。例えばインターフェースベースになっていないものとかにね。

Patrick Wright wrote:

Daniel, 君はまず自分の宿題をするべきだった。Springフレームワークバイトコード変換をAOPの機能性のために利用していない。AOP実装は完全にProxyベースで行われている。しかしSpringは実行時にバイトコードを自動生成することもできる。java.lang.reflect.Proxyでは扱えないProxyクラスのためにだ。例えばインターフェースベースになっていないものとかにね。

僕の理解だとどっちのアプローチも使えるはずだ。僕らはProxyを使っていたけど、AspectJ方式にスイッチした。そのほうが実行時によりスムースにつなげてくれる。僕らにとってそうすることがProxyクラス内部での呼び出しの実行時の問題をいくらか防いでくれた。とりあえず、選択肢があるってこと。少なくともSpring2には。

Patrick Wright wrote:

さらに悪いことに、多くのフレームワーク開発者はバイトコード変換ライブラリをフレームワークの主要な機能を扱うために利用してしまっている

それは正しい。それで実際に君はどんな問題を経験したの?
僕はコンパイル時や実行時にバイトコード変換を行ういくらかのライブラリを信用している。今までにそれらが原因のバグを経験したこともない。僕らが経験した唯一のバグはjarのバージョン管理の問題だった。2つのライブラリが互換性のないバージョンのバイトコードライブラリを利用していた。(ASMが共通の問題だ)でもJarJar*6を利用することで乗り越えたけど。

君の議論は少し仮定的すぎる。僕はもっと経験に基づいたバイトコード生成の議論を聞きたいな。実際にバイトコード生成が何を許しているのかを。

Regards
Patrick

JAlexoid wrote:

バイトコード変換はJavaコードの挙動をその仕様に反して実行時に変えてしまう

DIフレームワークも同じことをする。Reflectionも同じ効果を持つ。なぜならばprivateな変数に値を設定できるし、privateメソッドを呼ぶこともできるから。
銃みたいなものでそれを与えられて自分自身を撃つかどうかは君の選択だ。*7


利点はいくらでもある。特に実行速度に関して。リフレクションのオーバヘッドがないから。
欠点は新人がこの「クール」な機能に触れると結果は最悪なことになる。私たちのRubyの友人がnilオブジェクトが気にいらないのでそれを変更してしまったときみたいに。


個人的にはバイトコード変換をプロパティ変更のイベント駆動や真に透過的なDIに用いるのが好きだ。ASM*8は最高。EclipseのASMified Bytecode View*9もね。

Ian Griffiths DeveloperZone Top 100 wrote:

私はバイトコード生成だけで問題を経験したことがない。時々使っているけどうまくいっている。ある程度、JavaのProxyクラスが同じことをすると主張する人もいるだろう。

私が問題を経験したのは外部のスペシャリストによる監査が必要なセキュアなシステムの件だ。この人たちは一般的なセキュリティの知識やどんなループホールを探さなければいけないか知っている。しかしながら数万行もあるビジネスコードに直面すると彼らは仕事を放棄して、それがどのように動くのか理解したり、何も悪いことが起こらないことを確認することを行わない。

もしあなたが動的バイトコード生成を行うモジュールを彼らにプレゼンすれば、彼らが全く認めない可能性はとても高いだろう。
同じことは並行動作するコードでも言えるだろう。

Ian

Jose María Arranz Santamaría wrote:

もし私たちがPOJOの世界に住んでいるのならバイトコード実装はPOJO指向開発にて要求される魔法を得るのにベストなツールだろう。
バイトコード実装(変換、weaving、拡張、等)は万人向けではない。POJOをサービスに変換、または複数のサービスに展開する場合、またはAOPに大変有効だ。(しかしあまり多くの人はAOPを使っていないと思うけど)

リフレクションとProxyは悪くないがすぐに物足らなくなるし遅い。(もちろん、もし私たちがデータベースアクセスは問題ではないと話しているのならばだ。)

私がJNIEasy*10の開発を始めた時、私はリフレクションを試し、Proxyを評価した。しかし私は「真にネイティブで透過的な」そしてほとんど「制約のない」ものを欲した。永続性の世界ではObjectStore C++は夢だった。(C++で!)。JDOは同じObjectStoreと同じ透過性をバイトコード拡張にて獲得していた。バイトコード拡張はC++で夢見られた機能だ。(ObjectStoreはメモリページフォルトと必要なソースコードを利用した)Terracottaクラスタリングの魔法を与えるためにバイトコード拡張を必要とした。もし私たちが真の透過性を必要とするならばバイトコード変換がベストだ。

デバッグとベストプラクティスについて
バイトコード変換は極少でなければならない。基本的にはフックだ。インタセプタを呼ぶ。フィールド変数へのアクセスの前後やJavaコードの呼び出しや、それ全体の置き換えだ。新しいコードはif分岐を持ってはいけない。どんな決定も通常のJavaコードで行われるべきだ。(何もしないを含めてだ。)

変更されたバイトコードデバッグする場合、通常は変更されたソースを必要とはしない。なぜならばそんなことは馬鹿馬鹿しいし、ドキュメントに存在するべきだからだ。(いつフィールドがリードされるのかとかとか)通常変更されたバイトコードデバッグ情報を持たないがそれは問題ではない。なぜならばどんなインタセプタコールも通常のJavaコードなので問題なくデバッグできるからだ。とりあえずJODE*11のような逆アセンブラは便利だろう。

例として、

  int a = this.data;

は以下のように拡張されるかもしれない。

  int a = MyInterceptor.processIntField(this.data);

デバッグ上の問題とはなんだろうか?
processIntField呼び出しはデバッグできる。オリジナルの引数値はわかる。結果もわかる。

バイトコード実装において最も簡単なツールは私にとってJavassist*12だ。JavassistではJavaを使うことができる。バイトコードの形式を知る必要がない。なぜならばJavassistJavaコンパイラを持っているからだ。

Eric Samson wrote:

Jose Mariaに全力で同意する。
Danielはバイトコード実装をただの拡張的なコンパイラステップだと思うべきだ。
いくらでも良いライブラリがバイトコード実装するためにある。BCELとか、ASMとか。
結果的なバイトコードは依然100%JVM仕様に準拠している。プロファイルとデバッグAPIを含めてだ。(心配しないで。貴方のコードはデバッグするときに変更されたりしないから :-)

Rgds, Eric.

Ronald Miura wrote:

1. いかなる形式のバイトコード操作もJVMの仕様外のことを実行させることはできない(バグがなければね。:)
それはコードの挙動を変えうるけれども、ソースが言うことに関連してだし、変更後のバイトコードJVMに有効性を認められない限り実行すらされない。


2.バイトコード生成・操作による副作用の問題はフレームワークが何か間違いを起こすと発生するかもしれない。しかしそれは他のバグの問題と同じようなものだ。つまりHibernateのクラス生成アルゴリズムのバグはあなたのActiveObjectの動的Proxyのバグと変わらない。(それはCGLibを用いた動的クラス生成とまったく同じように働く)バグを直さなければならないだけの話であり、技術を責める話ではない。

私は確かに行き過ぎた「魔法」は好まない。私はバイトコード生成や操作が決定的(予想可能な)方法で使われる場合、それらを使用するべきではないとは思わない。使用者が本当に何をしているのか理解した上で行うべきだ。そして私はGavinとRod*13は彼ら自身の行っていることを理解していると強く信じる。:)

Tom wrote:

君の自由だ。
もしAspectJHibernateが君のデバッグをより難しくするだけで、得られる利益よりも大きいのなら使わなければいい。この機能が君にとって十分に重要ならSunに働きかけて言語や実行環境仕様がより良くなるよう採用してもらえるよう働きかければいい。

Brian S O'Neill wrote:

もちろんバイトコード変換を使うべきだ。潜在的に害悪だって?イエスだ。デバッグが難しくなる?イエスだ。利益があるのかだって?イエスだ。もっとも大きなリスクは君が完全にバイトコード変換に依存したコードを書いたときに発生する。そうでなければ誤った動作を書いた時だ。


私がプロジェクトのjavadocを探しているとして、良さそうな検索クラスを見つけたとする。私はコンストラクタを呼んで、いくつかのメソッドを呼んで、クラスが正常に使用できる前にこれをバイトコード変換に渡すだけだ。その間に私は何か悪いことを行っただろうか?何かクリティカルなデータを破壊しただろうか?

バイトコード変換に依存する
I might be looking around a project's javadocs and I find a useful looking class. I call the constructor, call some methods, and little do I know that I was supposed to pass this thing to a bytecode modifier before the class can be used correctly. In the meantime, what harm have I caused? Have I destroyed critical data?

Code that depends on bytecode modification should consider having a special mode indicating that they're not ready yet. Perhaps they can throw an exception, and the bytecode modification step sees the exception and removes it. Perhaps it can require it to be there.

public void doStuff() {
BytecodeModififerCheck.isModified();
...
}

The implementation of the isModified method can simply always throw an exception. The modifier of a method requires that this line of code be present at the very start. It removes it afterwards, so now the modified method works correctly.
Brian S O'Neill
carbonado.sourceforge.net
12 . At 11:49 AM on Sep 19, 2007, Jose María Arranz Santamaría wrote:
Click to reply to this thread Reply
Re: Should We Use Bytecode Modification?
> Code that depends on bytecode modification should
> consider having a special mode indicating that
> they're not ready yet.

I think you misunderstand the bytecode modification, byte code modification occurs *before* the class is ready to be used, may be modified in the file system or on loading time by the class loader. There is no possibility to modify a class already loaded (and registered on the JVM), in fact bytecode may be converted to machine code by the JVM JIT.

In the JDO world (and in JPA as in JPOX) an object may be "transient", when you make it "persistent" no bytecode modification occurs, this object is already "persistent capable" and the class is already enhanced, if not "persistent" any interceptor does nothing, when "persisted" internal object interceptors attach/synchronize this object to the database.

With JNIEasy is much the same when a "native capable" object is made "native" is attached/synchronized with a native memory fragment, if not native internal artifacts do nothing.
13 . At 4:31 PM on Sep 19, 2007, Tom wrote:
Click to reply to this thread Reply
Re: Should We Use Bytecode Modification?
There is no possibility to modify a class already loaded (and registered on the JVM), in fact bytecode may be converted to machine code by the JVM JIT.

"Conversion to machine code" is not an irreversible step in a JIT: if you change the byte code that gave rise to some piece of machine code, the machine code just gets invalidated and the JIT recompiles your new bytecode next time around. It has to be able to do that anyway even without bytecode modifications to be able to perform many kinds of optimizations.

And, yes, Java lets you (or will soon let you) change classes on the fly; it's needed for debugging and patching long-running jobs.
14 . At 8:09 PM on Sep 19, 2007, Bryan Taylor wrote:
Click to reply to this thread Reply
Re: Should We Use Bytecode Modification?
Bytecode enhancement DOES NOT change the behavior of your Java code counter to its specification at runtime. A class that is not declared final is specified to ALLOW polymorphism. Learn your OO.

Bytecode generation is simply one of many ways to produce valide bytecode. The technique happens to do this at runtime, but once produced, the bytecode is no different than any other. It happens not to have java source code. Neither does bytecode created by compiling groovy or scala or any of a dozen other mechanisms. If it's the runtime creation part you don't like, you must hate that the JDK 6 has a compiler API that allows java source code to be compiled at runtime. Of course producing the bytecode is not the real crux of the matter, as bytecode must be loaded by a class loader to run. Maybe this is your objections. If so, please stop using application servers with class loaders that allow you to load webapps at runtime. Or any program with hot loadable plugins.

Replies: 19 - Pages: 2 [ 1 2 | Next ] Threads: [ Previous | Next ]

thread.rss_message
[Reply to this Thread] [Home] [Up to Thread List] [Next Thread] [Next Page of Replies] [No Previous Replies] [Previous Thread] [No Email this thread to a friend]


Get Firefox! Powered by Jive Software

更新途中ですが明日も早いのでこのへんで

気が向いたら続けます。
なんでこの記事を一生懸命翻訳しているかというと、3年前にこれと同じ議論をした思い出があるからです。
ディフェンシブな開発 〜 SIビジネスの致命的欠陥 - kuranukiの日記(移転しました)→ http://kuranuki.sonicgarden.jpにもあるとおり、SI業界というのは本当に保守的で私も散々つまらない議論をしたものです。

*1:Danielは新進のフレームワークプログラマであり、新しいORMフレームワーク、””の作者

*2:言わずと知れたHibernate, JBoss seamの作者

*3:和訳がバイトコード変換で統一されているかわからないが、JVM上で実行するバイトコードを直接書き換えて機能の追加や削除を行う手法。AOPやORM等に用いられる。バイトコードに対してコンパイルを行うポストコンパイル方式や、実行時に動的に操作を行うダイナミックコンパイラ方式が存在する。AspectJJavassist等が有名。SpringやSeaserHibernate等、数多くのフレームワークがこれを利用している。

*4:http://cglib.sourceforge.net/

*5:Eclipseの差分Javaコンパイラ

*6:http://tonicsystems.com/products/jarjar/

*7:あまり良くない例え話。運が悪いと話が発散して収まらなくなる。政治的にセンシティブな話は例え話には向かないと思う

*8:http://asm.objectweb.org/

*9:http://asm.objectweb.org/eclipse/index.html

*10:http://www.innowhere.com/

*11:http://jode.sourceforge.net/

*12:http://www.csg.is.titech.ac.jp/~chiba/javassist/

*13:Rod Johnson:Springの作者