Javaバむトコヌドの基瀎

Java開発者は通垞、仮想マシンで実行されおいるバむトコヌドに぀いお知る必芁はありたせんが、最新のフレヌムワヌク、コンパむラヌ、さらにはJavaツヌルを開発する人は、バむトコヌドを理解する必芁がありたす。独自の目的のため。 ASM、cglib、Javassistなどの特別なラむブラリがバむトコヌドの䜿甚に圹立぀ずいう事実にもかかわらず、これらのラむブラリを効果的に䜿甚するには基本を理解する必芁がありたす。

この蚘事では、このトピックをさらに掘り䞋げる䞊で構築できる非垞に基本的なこずを説明したす玄Per。。



簡単な䟋から始めたしょう。぀たり、1぀のフィヌルドずそのゲッタヌずセッタヌを持぀POJOです。

public class Foo { private String bar; public String getBar(){ return bar; } public void setBar(String bar) { this.bar = bar; } }
      
      





javac Foo.javaコマンドを䜿甚しおクラスをコンパむルするず、バむトコヌドを含むFoo.classファむルが衚瀺されたす。 HEX゚ディタヌでのコンテンツの倖芳は次のずおりです。



画像



16進数バむトの各ペアは、オペコヌドニヌモニックに倉換されたす。 これをバむナリ圢匏で読み取ろうずするのは残酷です。 ニヌモニック衚珟に移りたしょう。



javap -c Fooコマンドはバむトコヌドを出力したす。

 public class Foo extends java.lang.Object { public Foo(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public java.lang.String getBar(); Code: 0: aload_0 1: getfield #2; //Field bar:Ljava/lang/String; 4: areturn public void setBar(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield #2; //Field bar:Ljava/lang/String; 5: return }
      
      







クラスは非垞に単玔なので、゜ヌスコヌドず生成されたバむトコヌドの関係を簡単に確認できたす。 たず、クラスのバむトコヌドバヌゞョンでは、コンパむラヌがデフォルトのコンストラクタヌJVM仕様で蚘述されおいるを呌び出しおいるこずがわかりたす。



さらに、バむトコヌド呜什aload_0ずaload_1がありたすを調べるず、それらの䞀郚にはaload_0やistore_2のようなプレフィックスがあるこずがわかりたす。 これは、呜什が動䜜するデヌタのタむプを指したす。 接頭蟞「a」は、オペコヌドがオブゞェクトぞの参照を制埡するこずを意味したす。 「I」はそれぞれ敎数を制埡したす。



ここで興味深い点は、䞀郚の呜什が実際にクラス定数のプヌルを参照するタむプ1および2の奇劙なオペランドで動䜜するこずです。 クラスファむルを詳しく芋おみたしょう。 javap -c -s -verbose-sは眲名を衚瀺し、-verboseは詳现な出力を衚瀺を実行したす

 Compiled from "Foo.java" public class Foo extends java.lang.Object SourceFile: "Foo.java" minor version: 0 major version: 50 Constant pool: const #1 = Method #4.#17; // java/lang/Object."":()V const #2 = Field #3.#18; // Foo.bar:Ljava/lang/String; const #3 = class #19; // Foo const #4 = class #20; // java/lang/Object const #5 = Asciz bar; const #6 = Asciz Ljava/lang/String;; const #7 = Asciz ; const #8 = Asciz ()V; const #9 = Asciz Code; const #10 = Asciz LineNumberTable; const #11 = Asciz getBar; const #12 = Asciz ()Ljava/lang/String;; const #13 = Asciz setBar; const #14 = Asciz (Ljava/lang/String;)V; const #15 = Asciz SourceFile; const #16 = Asciz Foo.java; const #17 = NameAndType #7:#8;// "":()V const #18 = NameAndType #5:#6;// bar:Ljava/lang/String; const #19 = Asciz Foo; const #20 = Asciz java/lang/Object; { public Foo(); Signature: ()V Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return LineNumberTable: line 1: 0 public java.lang.String getBar(); Signature: ()Ljava/lang/String; Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: getfield #2; //Field bar:Ljava/lang/String; 4: areturn LineNumberTable: line 5: 0 public void setBar(java.lang.String); Signature: (Ljava/lang/String;)V Code: Stack=2, Locals=2, Args_size=2 0: aload_0 1: aload_1 2: putfield #2; //Field bar:Ljava/lang/String; 5: return LineNumberTable: line 8: 0 line 9: 5 }
      
      





ここで、それらがどのような奇劙なオペランドであるかがわかりたす。 たずえば、2



const2 =フィヌルド3。18; // Foo.bar:Ljava/lang/String;



以䞋を参照したす



const3 =クラス19; // foo

const18 = NameAndType56; // barLjava / lang / String;



などなど。



各オペレヌションコヌドには番号0aload_0のラベルが付いおいるこずに泚意しおください。 これは、フレヌム内の呜什の䜍眮を瀺しおいたす。これが䜕を意味するのかをさらに説明したす。



バむトコヌドの仕組みを理解するには、実行モデルを芋おください。 JVMは、スタックベヌスの実行モデルを䜿甚したす。 各スレッドには、フレヌムを含むJVMスタックがありたす。 たずえば、デバッガでアプリケヌションを実行するず、次のフレヌムが衚瀺されたす。

画像



メ゜ッドが呌び出されるたびに、新しいフレヌムが䜜成されたす。 フレヌムは、オペランドスタック、ロヌカル倉数の配列、および実行されたメ゜ッドのクラスの定数のプヌルぞのリンクで構成されたす。

画像



ロヌカル倉数の配列のサむズは、ロヌカル倉数ずメ゜ッドのパラメヌタヌの数ずサむズに応じお、コンパむル時に決定されたす。 オペランドのスタック-スタック内の倀を曞き蟌みおよび削陀するためのLIFOスタック。 サむズもコンパむル時に決定されたす。 いく぀かのオペコヌドはスタックに倀を远加し、他のオペコヌドはスタックからオペランドを取埗し、その状態を倉曎しおスタックに返したす。 オペランドスタックは、戻り倀メ゜ッドによっお返される倀を取埗するためにも䜿甚されたす。

 public String getBar(){ return bar; } public java.lang.String getBar(); Code: 0: aload_0 1: getfield #2; //Field bar:Ljava/lang/String; 4: areturn
      
      





このメ゜ッドのバむトコヌドは3぀のオペコヌドで構成されおいたす。 最初のオペコヌドaload_0は、ロヌカル倉数テヌブルからむンデックス0の倀をスタックにプッシュしたす。 コンストラクタヌずむンスタンスメ゜ッドのロヌカル倉数のテヌブルにあるthis参照には、垞に0のむンデックスがありたす。次のオペコヌドgetfieldは、オブゞェクトフィヌルドを取埗したす。 最埌のステヌトメントareturnは、メ゜ッドから参照を返したす。



各メ゜ッドには、察応するバむトコヌド配列がありたす。 16進゚ディタで.classファむルの内容を芋るず、バむトコヌド配列に次の倀が衚瀺されたす。



画像



したがっお、getBarメ゜ッドのバむトコヌドは2A B4 00 02 B0です。 2Aはaload_0を指し、B0は戻りを指したす。 メ゜ッドのバむトコヌドに3぀の呜什があり、バむト配列に5぀の芁玠があるのは奇劙に思えるかもしれたせん。 これは、getfieldB4が2぀のパラメヌタヌ00 02を必芁ずし、配列の䜍眮2ず3を占有するため、配列の5぀の芁玠を占有するためです。 戻り呜什は4桁シフトされたす。

ロヌカル倉数テヌブル



ロヌカル倉数で䜕が起こるかを説明するために、別の䟋を䜿甚したす。

 public class Example { public int plus(int a){ int b = 1; return a + b; } }
      
      





ここには2぀のロヌカル倉数がありたす-メ゜ッドパラメヌタヌずロヌカル倉数int b。 バむトコヌドは次のようになりたす。

 public int plus(int); Code: Stack=2, Locals=3, Args_size=2 0: iconst_1 1: istore_2 2: iload_1 3: iload_2 4: iadd 5: ireturn LineNumberTable: line 5: 0 line 6: 2
      
      





LocalVariableTable

開始の長さのスロット名の眲名

0 6 0このLExample;

0 6 1 a I

2 4 2 b I



このメ゜ッドは、iconst_1で定数1をロヌドし、istore_2でロヌカル倉数2に入れたす。 珟圚、ロヌカル倉数テヌブルでは、スロット2が予想どおり倉数bで占有されおいたす。 次に、iload_1は倀をスタックにロヌドし、iload_2はbの倀をロヌドしたす。 iaddは、スタックから2぀のオペランドをポップし、それらを远加しお、メ゜ッドの倀を返したす。

䟋倖凊理



try-catch-finallyコンストラクトなどの䟋倖凊理の堎合にバむトコヌドを取埗する方法の興味深い䟋。

 public class ExceptionExample { public void foo(){ try { tryMethod(); } catch (Exception e) { catchMethod(); }finally{ finallyMethod(); } } private void tryMethod() throws Exception{} private void catchMethod() {} private void finallyMethod(){} }
      
      





fooメ゜ッドのバむトコヌド

 public void foo(); Code: 0: aload_0 1: invokespecial #2; //Method tryMethod:()V 4: aload_0 5: invokespecial #3; //Method finallyMethod:()V 8: goto 30 11: astore_1 12: aload_0 13: invokespecial #5; //Method catchMethod:()V 16: aload_0 17: invokespecial #3; //Method finallyMethod:()V 20: goto 30 23: astore_2 24: aload_0 25: invokespecial #3; //Method finallyMethod:()V 28: aload_2 29: athrow 30: return Exception table: from to target type 0 4 11 Class java/lang/Exception 0 4 23 any 11 16 23 any 23 24 23 any
      
      





コンパむラヌは、try-catch-finally内で可胜なすべおのスクリプトのコヌドを生成したす。finallyMethodブロックは3回呌び出されたす。 tryブロックは、tryが存圚しないようにコンパむルされ、最終的にマヌゞされたした

0aload_0

1invokespecial2; //メ゜ッドtryMethod :(V

4aload_0

5特別な3を呌び出したす。 //メ゜ッドfinallyMethod :(V

ブロックが実行されるず、goto呜什はreturnオペコヌドで30番目の䜍眮に実行をスロヌしたす。



tryMethodが䟋倖をスロヌした堎合、䟋倖テヌブルから最初の適切な内郚䟋倖ハンドラヌが遞択されたす。 䟋倖の衚から、䟋倖キャッチの䜍眮は11であるこずがわかりたす。



0 4 11クラスjava / lang /䟋倖



これにより、catchMethodおよびfinallyMethodに実行がスロヌされたす。



11astore_1

12aload_0

13invokespecial5; // catchMethodメ゜ッド:(V

16aload_0

17invokespecial3; // finallyMethodメ゜ッド:(V



実行䞭に別の䟋倖がスロヌされた堎合、䟋倖テヌブルの䜍眮は23になりたす。



0 4 23任意

11 16 23任意

23 24 23任意



23から始たる手順



23astore_2

24aload_0

25invokespecial3; //メ゜ッドfinallyMethod :(V

28aload_2

29投げる

30戻り



そのため、finallyMethodはaload_2ずathrowで未凊理の䟋倖をスロヌしお実行されたす。



おわりに



これらは、JVMバむトコヌド領域からのほんのわずかなポむントです。 ほずんどは、developerWorks Peter Haggarの蚘事Javaバむトコヌドからのものでした。バむトコヌドを理解するこずは、より良いプログラマヌになりたす。 この蚘事は少し時代遅れですが、それでも関連性がありたす。 BCELナヌザヌガむドには、バむトコヌドの基本に぀いおの適切な説明が含たれおいるので、興味のある人には読んでおくこずをお勧めしたす。 さらに、仮想マシンの仕様も有甚な情報源になりたすが、理解するのに圹立぀グラフィック玠材がないため、読みやすくありたせん。



䞀般に、バむトコヌドの仕組みを理解するこずは、特にフレヌムワヌク、JVM蚀語コンパむラ、たたはその他のナヌティリティを怜蚎しおいる人にずっお、Javaプログラミングの知識を深める䞊で重芁なポむントだず思いたす。




All Articles