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メソッドregisterNativesObjectクラスのバイトコードにも示されていることがわかります。

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. 設定

まず、最新のasmasm-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. バイトコードを表示

次に、ClassReaderTraceClassVisitorを使用して、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利用できます。