ホーム > デベロッパ > J2ME / MIDP プログラミング > 第 4 回 その2

第4回:実機に載せよう(ダウンサイズ)

クラスファイルを覗こう

ツールを使用して説明用のクラス Temp のクラスファイルを覗いて見ます。BCEL( The Byte Code Engineering Library )というライブラリがあります。 BCEL は 簡単にクラスファイルの構造解析や変更ができます。 The Jakarta Projectよりダウンロードできます。

Class2HTML の使い方

BCEL には、クラスファイルの内容を HTML に変換する機能があります。

■ コマンドの形式

java -classpath bcel.jar org.apache.bcel.util.Class2HTML 
[-d 出力ディレクトリ名] [-zip JARファイル名] クラス名

(クラス名は複数可、スペースで分ける)

■ 例

java -classpath bcel.jar org.apache.bcel.util.Class2HTML -d ../html Temp.class

生成されたページの説明

上記のコマンドで html ディレクトリ内に生成されたTemp.htmlをみてみます。

フィールド&メソッド

private static final int field1 = 0 

private static int field2 

private int field3 

ソースファイルで宣言したものがクラスファイル中にもそのまま入っています。ここで field1 は0に初期化されているように見えますが、これは ConstantValue 属性がついていることを示しています。

メソッドは以下のようになっています。

public void <init> () 

public static void main (String[]) 

private void method1 () 

private int method2 () 

static void <clinit> () 

ここで宣言していない名前のメソッドが二つほどあります。 <init> メソッドは、コンストラクタです。ソース上の public Temp() にあたります。 <clinit> は static 初期化節( static ブロック)メソッドで、

static{

 ・・・

}

と書くブロックのメソッドです。

今回のソースに上記のような static ブロックはありませんが、 フィールドの宣言部分で

private static final int field1 = 0;

private staticint field2 = 1;

と初期化も行っていますが、この初期化を static ブロックで行ったことになっています。 つまり、上記ソースが

private static final int field1;

private staticint field2;

static{

 field1=0;

 field2=1;

}

と解釈されています。ソースコードでフィールドの宣言と同時に初期化を書いても、バイトコードでは初期化はメソッドに移されます。

それでは、

privateint field3 = 2;

この初期化はどこに行ったのか、といいますと、<init>メソッド(=コンストラクタ)に移っています。

バイトコード

一番上に public void <init>() とあるように、<init> の詳細です。 Attributes とあるのは、そのメソッドについている属性です。 <init> には Code 属性が、そしてその Code 属性には LineNumberTable と LocalVariableTable 属性がついています。 Code 属性はソース上でメソッド内に書いたコードがインストラクションに翻訳されたバイトコードが入っています。 LineNumberTable はそのバイトコードとソース上の行番号の対応がかかれています。 LocalVariableTable は、メソッド内で宣言した変数の名前などを保持したものです。 LineNumberTable と LocalVariableTable は主にデバッグ時に用いられ、実行時には不必要な属性です。

バイトコードをみてみます。 ソース上では中身は空っぽだったコンストラクタですが、六つの命令が入っています。

Byte

offset Instruction Argument 

0 aload_0

1 invokespecial Object.<init> ()V():void 

4 aload_0

5 iconst_2

6 putfield field3 I

9 return 

図4 ローカル変数とオペランドスタックの遷移

図4 にこれらのコード実行時のローカル変数とオペランドスタックの遷移を示します。

ローカル変数とオペランドスタックの遷移

最初の命令 "aload_0" は、0番目のローカル変数からオブジェクト参照を取り出してをスタック上に積む、というものです。 インスタンスメソッドの場合、メソッドが呼ばれたときの最初のローカル変数には、 0番目に this ポインタ、以降は引数が順に入っていきます。 コンストラクタもインスタンスメソッドでして、0番目には this ポインタが入っていますので、 this ポインタをスタック上に積んだことになります。

"invokespecial" は特定のインスタンスメソッドを呼び出す命令で、 ここでは Object.<init> を呼び出します。 この呼び出しで、先ほどスタックに積んだ this ポインタは降ろされます。

"aload_0" は最初の命令と同じで、スタックに this ポインタを再度積みます。 "iconst_2" は integer の2をスタックに積みます。ここでスタックは this ポインタ、integer の2、となります。

"putfield" はインスタンスフィールドに値を入れます。 この実行で先ほどスタックに積んだ this ポインタの field3 に integer の2を入れます。 そしてスタックは空になります。

最後に"return"でメソッド終了です。

private void method1(){

 int variable = field1+method2();

 System.out.println("method1:"+variable);

}

また、ソースコードでは上記のように method1 の中で field1 を参照していますが、バイトコード中では field1 への参照はありません。 つまり ConstantValue がついているフィールドはコンパイラによって値が展開されます。 (展開されたあと、残ったフィールドはサイズを増やすだけで、無駄にしかなりません。ソースコードで定数を展開するとサイズが削減できるのは、 この余分なフィールドが残らないためです。)

コンスタントプール

再度 <init> メソッドのバイトコードに戻ります。

3列目の Argument の項は表示上展開されていますが、バイトコード中ではコンスタントプールへの参照です。

"invokespecial" の Argument "Object.<init> ()V"の"<init> ()V" をクリックしてください。 左上のコンスタントプール一覧に出てくる 1 CONSTANT_Methodref が参照されていることがわかります。 "putfield" の Argument "field3 I" も2番目のコンスタントプールエントリ CONSTANT_Fieldref を参照しているのですが、残念ながら HTML からはわかりません。

表2で示したコンスタントプールの参照関係をこの "Object.<init> ()V" を例で具体的に見てみましょう。

図5 コンスタントプールエントリの参照関係例
コンスタントプールエントリの参照関係例

このようにデータ領域であるコンスタントプールを使うことによって、"0xB7 0x00 0x01" という3バイトで、 "invokespecial Object.<init> ()V" の呼び出しが可能となっています。 (invokespecial のオペコードは 0xB7)

属性

説明していない属性をみてみましょう。 1番の ConstantValue 属性は、フィールドの field1 についています。static final で定数の時につくものです。 16番の SourceFile 属性はクラス自体につく属性で、ソースファイル名を表します。

属性には「名前」があり、データ構造や内容は属性ごとに決まっています。 Code 属性では Java のバイトコード以外の多くの属性は実行時には不要です。コンパイルオプション -g:none やインストラクションの最適化でサイズ削減できます。

クラスファイルの変更

ソースに変更を加えて、クラスファイルのサイズを変えるのは有効ですが、限界があります。

Class Construction Kit

BCEL には、Class Construction Kit というツールも付属しています。クラスファイルをソースなしに直接生成したり、クラスファイルを編集するツールです。

次のメソッドをダウンサイズします。

private void connect(){

 HttpConnection http = null;

 DataInputStream input = null;

 int result;



 String temp = "/";



 try {

 http = (HttpConnection)Connector.open(temp);

 input = http.openDataInputStream();

 result = input.readInt();

 } catch (IOException ioe) {

 }



 try {

 if (input != null) {

input.close();

 }

 if (http != null) {

http.close();

 }

 } catch (IOException ioe) {

 }

}

通信するメソッドとしてはよくある形をしています。 これをバイトコードレベルで見ると以下のようになります。

private void connect()

Attributes
Code 
LineNumberTable 
LocalVariableTable 
Byte
offset Instruction Argument 
0 aconst_null   
1 astore_1   
2 aconst_null   
3 astore_2   
4 ldc "/" 
6 astore %4 
8 aload %4 
10 invokestatic javax.microedition.io.Connector.open
                (Ljava/lang/String;)Ljavax/microedition/io/Connection;
                (String):javax.microedition.io.Connection 
13 checkcast javax.microedition.io.HttpConnection 
16 astore_1   
17 aload_1   
18 invokeinterface javax.microedition.io.InputConnection.openDataInputStream            
                   ()Ljava/io/DataInputStream;():java.io.DataInputStream 
23 astore_2   
24 aload_2   
25 invokevirtual java.io.DataInputStream.readInt ()I():int 
28 istore_3   
29 goto 37 
32 astore %5 
34 goto 37 
37 aload_2   
38 ifnull 48 
41 aload_2   
42 invokevirtual java.io.DataInputStream.close ()V():void 
45 goto 48 
48 aload_1   
49 ifnull 66 
52 aload_1   
53 invokeinterface javax.microedition.io.Connection.close ()V():void 
58 goto 66 
61 astore %5 
63 goto 66 
66 return   
 
 
4 Code
Maximum stack size = 1 
Number of local variables = 6 
Byte code 
Exceptions handled 

java.io.IOException
(Ranging from lines 8 to 29, handled at line 32) 
java.io.IOException
(Ranging from lines 37 to 61, handled at line 61) 
 

インストラクションを変更してメソッドを短くする方法の例です。

インストラクションのオフセット 34 を見てください。 goto 37 となっていますが、オフセット 37 は次の行です。オフセット 34 を削除するには他から参照されていなか確認しなければいけません。オフセット 63 の goto も同様です。

オフセット 58 の goto は、return に飛びます。この goto を return に置き換えることで、3 バイトの goto が 1 バイトの return になります。

オフセット 32 の astore 5 です。ローカル変数の 5 番目にスタック上の値を積む、という命令です。さて、このメソッド内を見渡して、このローカル変数 5 番目の値を使用する個所があるでしょうか?実際には使われません。pop命令に置き換えてスタック上の値を捨ても構いません。オフセット 63 の astore 5 も同様です。

オフセット 6・8 のペアを見てください。astore 4 & aload 4 というこのペアは、スタックにある値を 4 番目のローカル変数に格納し(astore 4)、 そのローカル変数から値をスタックに積んでいます( aload 4 )。4 番目のローカル変数は使われませんので、オフセット 6・8 を消して構いません。4 番目のローカル変数が無くなるので、5 番目以降のローカル変数の添え字をずらす必要があります。

実際に Class Construction Kit を使ってダウンサイズします。 Class Construction Kit を用いるためには、まず BCEL に付属の CCK.jar を bcel.jar と同じフォルダに置きます。 そして CCK.jar を起動してください。CCK.jar をダブルクリック、もしくは以下のコマンドを実行してください。

    java -jar CCK.jar

メニューの File から open で編集したいクラスファイルを選択します。 左ウインドウのメソッド一覧から connect の Code を選んでください。インストラクション一覧が右ウインドウに出ます。( 図 4 )

図 4 Class Construction Kit
Class Construction Kit

消したい行をクリックして右に並ぶボタンの Remove を押せば消えますが、LineNumberGen がどうのこうの、というエラーダイアログが出ます。「この行を参照するものがあった」、ということです。メソッドの属性の LineNumberTable が参照しています。 LineNumberTable はデバッグ情報なので不要です。 左ウインドウのメソッド一覧から connect の LineNumberTable を選び、右ウインドウの項目全てを消せば完了です。 これで Instruction が削除できます。

編集後のインストラクション列は次のようになります。

private void connect()

Attributes
Code 
Byte
offset Instruction Argument 
0 aconst_null  
1 astore_1  
2 aconst_null  
3 astore_2  
4 ldc "/"
6 invokestatic javax.microedition.io.Connector.open (Ljava/lang/String;)
               Ljavax/microedition/io/Connection;
               (String):javax.microedition.io.Connection 
9 checkcast javax.microedition.io.HttpConnection 
12 astore_1   
13 aload_1   
14 invokeinterface javax.microedition.io.InputConnection.openDataInputStream 
                   ()Ljava/io/DataInputStream;():java.io.DataInputStream 
19 astore_2   
20 aload_2   
21 invokevirtual java.io.DataInputStream.readInt ()I():int 
24 istore_3   
25 goto 29 
28 pop   
29 aload_2   
30 ifnull 37 
33 aload_2   
34 invokevirtual java.io.DataInputStream.close ()V():void 
37 aload_1   
38 ifnull 49 
41 aload_1   
42 invokeinterface javax.microedition.io.Connection.close ()V():void 
47 return   
48 pop   
49 return   
 
 
4 Code
Maximum stack size = 1
Number of local variables = 4
Byte code
Exceptions handled 

java.io.IOException
(Ranging from lines 6 to 25, handled at line 28)
java.io.IOException
(Ranging from lines 29 to 48, handled at line 48)