1. 概要
バイトコード分析は、コードの問題の発見、コードプロファイリング、特定のアノテーションを持つクラスの検索など、多くの理由でJava開発者の間で一般的な方法です。
この記事では、Javaでクラスファイルのバイトコードを表示する方法について説明します。
2. バイトコードとは何ですか?
バイトコードはJavaプログラムの中間表現であり、JVMがプログラムをマシンレベルのアセンブリ命令に変換できるようにします。
Javaプログラムをコンパイルすると、バイトコードが.classファイルの形式で生成されます。 この.classファイルには実行不可能な命令が含まれており、解釈されるのはJVMに依存しています。
3. javapを使用する
Javaコマンドラインには、クラスファイルのフィールド、コンストラクタ、およびメソッドに関する情報を表示するjavapツールが付属しています。
使用されるオプションに基づいて、クラスを分解し、Javaバイトコードを構成する命令を表示できます。
3.1. javap
javap コマンドを使用して、最も一般的なObjectクラスのバイトコードを表示してみましょう。
$ javap java.lang.Object
コマンドの出力には、Objectクラスの最低限の構成が表示されます。
public class java.lang.Object {
public java.lang.Object();
public final native java.lang.Class<?> getClass();
public native int hashCode();
public boolean equals(java.lang.Object);
protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException;
public java.lang.String toString();
public final native void notify();
public final native void notifyAll();
public final native void wait(long) throws java.lang.InterruptedException;
public final void wait(long, int) throws java.lang.InterruptedException;
public final void wait() throws java.lang.InterruptedException;
protected void finalize() throws java.lang.Throwable;
static {};
}
デフォルトでは、バイトコード出力には、プライベートアクセス修飾子を持つフィールド/メソッドは含まれません。
3.2. javap -p
すべてのクラスとメンバーを表示するには、-p引数を使用できます。
public class java.lang.Object {
public java.lang.Object();
private static native void registerNatives();
public final native java.lang.Class<?> getClass();
public native int hashCode();
public boolean equals(java.lang.Object);
protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException;
// ...
}
ここでは、privateメソッドregisterNativesがObjectクラスのバイトコードにも示されていることがわかります。
3.3. javap -v
同様に、 -v引数を使用して、スタックサイズやObjectクラスのメソッドの引数などの詳細情報を表示できます。
Classfile jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/Object.class
Last modified Mar 15, 2017; size 1497 bytes
MD5 checksum 5916745820b5eb3e5647da3b6cc6ef65
Compiled from "Object.java"
public class java.lang.Object
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #49 // java/lang/StringBuilder
// ...
{
public java.lang.Object();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 37: 0
public final native java.lang.Class<?> getClass();
descriptor: ()Ljava/lang/Class;
flags: ACC_PUBLIC, ACC_FINAL, ACC_NATIVE
Signature: #26 // ()Ljava/lang/Class<*>;
// ...
}
SourceFile: "Object.java"
3.4. javap -c
また、 javapコマンドでは、-c引数を使用してJavaクラス全体を逆アセンブルできます。
Compiled from "Object.java"
public class java.lang.Object {
public java.lang.Object();
Code:
0: return
public boolean equals(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: if_acmpne 9
5: iconst_1
6: goto 10
9: iconst_0
10: ireturn
protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException;
// ...
}
さらに、 javap コマンドを使用すると、さまざまな引数を使用して、システム情報、定数、および内部型アノテーションを確認できます。
-help 引数を使用すると、javapコマンドでサポートされているすべての引数を一覧表示できます。
クラスファイルのバイトコードを表示するためのJavaコマンドラインソリューションを見てきました。次に、いくつかのバイトコード操作ライブラリを調べてみましょう。
4. ASMの使用
ASM は、人気のあるパフォーマンス指向の低レベルJavaバイトコード操作および分析フレームワークです。
4.1. 設定
まず、最新のasmとasm-utilMavenの依存関係をpom.xmlに追加しましょう。
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>8.0.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>8.0.1</version>
</dependency>
4.2. バイトコードを表示
次に、ClassReaderとTraceClassVisitorを使用して、Objectクラスのバイトコードを表示します。
try {
ClassReader reader = new ClassReader("java.lang.Object");
StringWriter sw = new StringWriter();
TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.out));
reader.accept(tcv, 0);
} catch (IOException e) {
e.printStackTrace();
}
ここで、 TraceClassVisitorオブジェクトは、バイトコードを抽出して生成するためにPrintWriterオブジェクトを必要とすることに注意してください。
// class version 52.0 (52)
// access flags 0x21
public class java/lang/Object {
// compiled from: Object.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 37 L0
RETURN
MAXSTACK = 0
MAXLOCALS = 1
// access flags 0x101
public native hashCode()I
// access flags 0x1
public equals(Ljava/lang/Object;)Z
L0
LINENUMBER 149 L0
ALOAD 0
ALOAD 1
IF_ACMPNE L1
ICONST_1
GOTO L2
L1
// ...
}
5. BCELの使用
一般にApacheCommons BCEL として知られているバイトコードエンジニアリングライブラリは、Javaクラスファイルを作成/操作するための便利な方法を提供します。
5.1. Mavenの依存関係
いつものように、最新の bcelMaven依存関係をpom.xmlに追加しましょう。
<dependency>
<groupId>org.apache.bcel</groupId>
<artifactId>bcel</artifactId>
<version>6.5.0</version>
</dependency>
5.2. クラスを分解してバイトコードを表示する
次に、 Repository クラスを使用して、JavaClassオブジェクトを生成できます。
try {
JavaClass objectClazz = Repository.lookupClass("java.lang.Object");
System.out.println(objectClazz.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
ここでは、objectClazzオブジェクトでtoStringメソッドを使用して、バイトコードを簡潔な形式で表示しています。
public class java.lang.Object
file name java.lang.Object
compiled from Object.java
compiler version 52.0
access flags 33
constant pool 78 entries
ACC_SUPER flag true
Attribute(s):
SourceFile: Object.java
14 methods:
public void <init>()
private static native void registerNatives()
public final native Class getClass() [Signature: ()Ljava/lang/Class<*>;]
public native int hashCode()
public boolean equals(Object arg1)
protected native Object clone()
throws Exceptions: java.lang.CloneNotSupportedException
public String toString()
public final native void notify()
// ...
さらに、 JavaClassクラスは、getConstantPool、getFields、getMethodsなどのメソッドを提供して、分解されたクラスの詳細を表示します。
assertEquals(objectClazz.getFileName(), "java.lang.Object");
assertEquals(objectClazz.getMethods().length, 14);
assertTrue(objectClazz.toString().contains("public class java.lang.Object"));
同様に、 set*メソッドはバイトコード操作に使用できます。
6. Javassistの使用
また、Javaバイトコードを表示/操作するための高レベルAPIを提供する Javassist ( Javaプログラミングアシスタント)ライブラリを使用できます。
6.1. Mavenの依存関係
まず、最新の javassistMaven依存関係をpom.xmlに追加します。
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
6.2. ClassFileを生成します
次に、ClassPoolおよびClassFileクラスを使用して、Javaクラスを生成できます。
try {
ClassPool cp = ClassPool.getDefault();
ClassFile cf = cp.get("java.lang.Object").getClassFile();
cf.write(new DataOutputStream(new FileOutputStream("Object.class")));
} catch (NotFoundException e) {
e.printStackTrace();
}
ここでは、 write メソッドを使用しました。これにより、DataOutputStreamオブジェクトを使用してクラスファイルを書き込むことができます。
// Compiled from Object.java (version 1.8 : 52.0, super bit)
public class java.lang.Object {
// Method descriptor #19 ()V
// Stack: 0, Locals: 1
public Object();
0 return
Line numbers:
[pc: 0, line: 37]
// Method descriptor #19 ()V
private static native void registerNatives();
// Method descriptor #24 ()Ljava/lang/Class;
// Signature: ()Ljava/lang/Class<*>;
public final native java.lang.Class getClass();
// Method descriptor #28 ()I
public native int hashCode();
// ...
また、 ClassFile クラスのオブジェクトは、定数プール、フィールド、およびメソッドへのアクセスを提供します。
assertEquals(cf.getName(), "java.lang.Object");
assertEquals(cf.getMethods().size(), 14);
7. Jclasslib
さらに、IDEベースのプラグインを使用して、クラスファイルのバイトコードを表示できます。 たとえば、IntelliJIDEAで利用可能なjclasslibバイトコードビューアプラグインを調べてみましょう。
7.1. インストール
まず、[設定/設定]ダイアログを使用してプラグインをインストールします。
7.2. Objectクラスのバイトコードを表示する
次に、[表示]メニューの[Jclasslibでバイトコードを表示]オプションを選択して、選択したObjectクラスのバイトコードを表示できます。
次に、オブジェクトクラスのバイトコードを表示するダイアログが開きます。
7.3. 詳細を見る
また、Jclasslibプラグインダイアログを使用して、定数プール、フィールド、メソッドなどのバイトコードのさまざまな詳細を確認できます。
同様に、EclipseIDEを使用してクラスファイルのバイトコードを表示するためのBytecode VisualizerPluginがあります。
8. 結論
このチュートリアルでは、Javaでクラスファイルのバイトコードを表示する方法を検討しました。
まず、javapコマンドとそのさまざまな引数を調べました。 次に、バイトコードを表示および操作する機能を提供するいくつかのバイトコード操作ライブラリを調べました。
最後に、IntelliJIDEAでバイトコードを表示できるIDEベースのプラグインJclasslibを調べました。
いつものように、すべてのコード実装はGitHubで利用できます。