1. 概述

字节码分析是Java开发中常见的操作,用途广泛,比如排查代码问题、性能调优、查找带有特定注解的类等。

本文将系统介绍几种在Java中查看类文件字节码的方法,从命令行工具到第三方库,再到IDE插件,覆盖日常开发中的主要使用场景。

2. 什么是字节码?

字节码(Bytecode)是Java程序的中间表示形式,JVM通过它将Java代码翻译成机器级的汇编指令。

当Java源码被编译后,会生成 .class 文件,其中就包含了字节码。这些指令本身不可直接执行,必须依赖JVM进行解释或即时编译(JIT)。

✅ 字节码是平台无关的,这也是Java“一次编写,到处运行”的基础。
⚠️ 直接阅读字节码对排查某些底层问题(如泛型擦除、自动装箱、lambda实现)非常有帮助。

3. 使用 javap 命令

javap 是JDK自带的反编译工具,能查看类的字段、构造方法、方法签名,甚至反汇编出字节码指令。

3.1 基础用法:javap

查看 Object 类的基本结构:

$ javap java.lang.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 {};
}

⚠️ 默认情况下,javap 不会显示 private 成员,这对分析某些类可能不够用。

3.2 查看所有成员:javap -p

加上 -p 参数可显示所有访问级别的成员:

$ javap -p java.lang.Object

输出中会多出 private 方法:

private static native void registerNatives();

✅ 这个参数在分析JDK源码或第三方库时非常实用,能看清完整结构。

3.3 详细信息:javap -v

使用 -v 参数可输出更详细的字节码信息,包括常量池、栈大小、方法描述符等:

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"

✅ 这里能看到 major version 52 对应 JDK 8,对排查版本兼容问题很有帮助。

3.4 反汇编字节码:javap -c

使用 -c 参数可反汇编方法体,查看具体的字节码指令:

$ javap -c java.lang.Object

输出示例:

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

aload_0if_acmpne 这些就是JVM的字节码指令,理解它们有助于深入理解Java底层机制。

📌 小贴士:运行 javap -help 可查看所有支持的参数。

4. 使用 ASM 库

ASM 是一个高性能、低层级的字节码操作和分析框架,广泛用于AOP、热更新、代码生成等场景。

4.1 引入依赖

pom.xml 中添加 ASM 核心和工具包依赖:

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.4</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-util</artifactId>
    <version>9.4</version>
</dependency>

4.2 查看字节码

使用 ClassReaderTraceClassVisitor 输出 Object 类的字节码:

try {
    ClassReader reader = new ClassReader("java.lang.Object");
    TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.out));
    reader.accept(tcv, 0);
} catch (IOException e) {
    e.printStackTrace();
}

输出示例:

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

✅ ASM 输出格式清晰,适合程序化分析。
⚠️ 学习成本较高,适合需要深度操作字节码的场景。

5. 使用 BCEL

Apache Commons BCEL(Byte Code Engineering Library)提供了操作Java类文件的便捷方式,适合创建或修改类文件。

5.1 Maven 依赖

<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(); 
}

输出示例:

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 提供了 getMethods()getFields()getConstantPool() 等方法,便于程序化访问类结构。

6. 使用 Javassist

Javassist(Java Programming Assistant)提供了更高层的API,相比ASM更易用,适合快速实现字节码操作。

6.1 Maven 依赖

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

6.2 生成并查看 ClassFile

使用 ClassPoolClassFile 获取类信息:

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();
}

输出示例:

// 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();
  
  // ...

✅ Javassist API 更贴近Java语法,学习成本低。
ClassFile 对象可直接访问方法、字段、常量池等结构。

7. 使用 Jclasslib 插件

对于日常开发,使用IDE插件查看字节码是最直观的方式。以IntelliJ IDEA为例,jclasslib Bytecode Viewer 是一个非常实用的插件。

7.1 安装插件

进入 Settings → Plugins → Marketplace,搜索 jclasslib 并安装:

Screen-Shot-2020-06-01

7.2 查看 Object 类字节码

在编辑器中打开 Object.java,选择菜单 View → Show Bytecode With Jclasslib:

Screen-Shot-2020-06-01

插件会弹出字节码查看窗口:

Screen-Shot-2020-06-01

7.3 查看详细信息

插件支持查看常量池、字段、方法等详细信息,界面清晰,适合快速分析:

Screen-Shot-2020-06-01

✅ 开发中推荐使用此插件,效率远高于命令行。
📌 Eclipse 用户可使用 Bytecode Visualizer Plugin 实现类似功能。

8. 总结

本文介绍了多种查看Java类字节码的方式:

  • javap:JDK自带,简单粗暴,适合快速查看
  • ✅ ASM:高性能,底层操作,适合框架开发
  • ✅ BCEL:功能完整,API清晰,适合类文件工程化处理
  • ✅ Javassist:高层API,易上手,适合快速实现字节码增强
  • ✅ Jclasslib:IDE集成,可视化,日常开发首选

选择哪种方式取决于你的具体需求。如果是排查问题,推荐先用 javap -c 或 Jclasslib 插件;如果是做框架或中间件开发,ASM 或 Javassist 更合适。

所有示例代码已上传至 GitHub:https://github.com/yourname/java-bytecode-examples


原始标题:View Bytecode of a Class File in Java