在Java中查看class文件的字节码

2023/06/12

1. 概述

出于多种原因,字节码分析是Java开发人员的常见做法,例如查找代码问题、代码分析以及搜索具有特定注解的类。

在本文中,我们将探讨在Java中查看class文件的字节码的方法。

2. 什么是字节码?

字节码是Java程序的中间表示,允许JVM将程序翻译成机器级汇编指令

编译Java程序时,会以.class文件的形式生成字节码。此.class文件包含不可运行的指令并依赖于JVM进行解释。

3. 使用javap

Java命令行附带了javap工具,它显示有关class文件的字段、构造函数和方法的信息

根据使用的选项,它可以反汇编一个类并显示构成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 {};
}

默认情况下,字节码输出将不包含带有private访问修饰符的字段/方法。

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;
  // ...
}

在这里,我们可以观察到私有方法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命令支持的所有参数。

现在我们已经了解了用于查看class文件字节码的Java命令行解决方案,让我们来研究一些字节码操作库。

4. 使用ASM

ASM是一种流行的面向性能的低级Java字节码操作和分析框架。

4.1 设置

首先,让我们将最新的asmasm-util Maven依赖项添加到我们的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

字节码工程库(通常称为Apache Commons BCEL)提供了一种创建/操作Java类文件的便捷方法。

5.1 Maven依赖

像往常一样,让我们将最新的bcel Maven依赖项添加到我们的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

此外,我们可以使用提供高级API的Javassist(Java编程助手)库来查看/操作Java字节码。

6.1 Maven依赖

首先,我们将最新的javassist Maven依赖项添加到我们的pom.xml中:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.27.0-GA</version>
</dependency>

6.2 生成ClassFile

然后,我们可以使用ClassPoolClassFile类来生成一个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的插件来查看class文件的字节码。例如,让我们探索可用于IntelliJ IDEA的jclasslib字节码查看器插件。

7.1 安装

首先,我们将使用“Settings/Preferences”对话框安装插件:

7.2 查看Object类的字节码

然后,我们可以选择“View”菜单下的“Show Bytecode With Jclasslib”选项来查看所选Object类的字节码:

接下来,将打开一个对话框以显示Object类的字节码:

7.3 查看详情

此外,我们可以使用Jclasslib插件对话框查看字节码的各种细节,如常量池、字段和方法:

同样,我们有Bytecode Visualizer Plugin可以使用Eclipse IDE查看class文件的字节码。

8. 总结

在本教程中,我们探讨了在Java中查看class文件字节码的方法。

首先,我们检查了javap命令及其各种参数。然后,我们浏览了一些字节码操作库,这些库提供了查看和操作字节码的功能。

最后,我们研究了一个基于IDE的插件Jclasslib,它允许我们在IntelliJ IDEA中查看字节码。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章