javap
是 JDK(Java Development Kit)中提供的一个实用工具,用于反汇编 Java 类文件(.class
文件)。它可以帮助开发者查看类文件的字节码、方法签名、字段信息等,是学习 Java 虚拟机(JVM)字节码和调试代码的有力工具。
1. javap
的基本概念
javap
是一个命令行工具,用于对 Java 类文件进行反汇编。它读取 .class
文件,并将其内容以人类可读的形式输出。javap
的输出内容包括类的结构、字段、方法、字节码指令等。
- 类文件(
.class
文件):Java 源代码经过编译器(javac
)编译后生成的二进制文件。它包含了类的定义、方法的字节码等信息。 - 字节码(Bytecode):Java 虚拟机执行的中间代码。
javap
的主要功能之一就是将字节码以文本形式展示出来。
2. javap
的常用选项
javap
提供了许多选项,用于控制输出的内容和格式。以下是一些常用的选项:
2.1 -c
选项:显示字节码
这是 javap
最常用的功能之一,用于显示方法的字节码指令。
示例代码:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
编译并运行 javap
:
javac HelloWorld.java
javap -c HelloWorld
输出结果:
Compiled from "HelloWorld.java"
public class HelloWorld {
public HelloWorld();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, World!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
解释:
0: aload_0
:加载this
引用到操作数栈。1: invokespecial #1
:调用父类的构造方法。3: ldc #3
:加载字符串常量"Hello, World!"
。5: invokevirtual #4
:调用PrintStream.println
方法。
2.2 -p
选项:显示私有成员
默认情况下,javap
只显示类的公共成员(如公共字段和方法)。使用 -p
选项可以显示类的所有成员,包括私有成员。
示例代码:
public class PrivateExample {
private int secretNumber = 42;
private void secretMethod() {
System.out.println("This is a secret method.");
}
}
运行 javap
:
javac PrivateExample.java
javap -p PrivateExample
输出结果:
Compiled from "PrivateExample.java"
public class PrivateExample {
private int secretNumber;
private void secretMethod();
}
2.3 -s
选项:显示字段和方法的内部类型签名
这个选项会显示字段和方法的内部类型签名,这对于理解泛型和复杂类型非常有帮助。
示例代码:
public class GenericExample {
private List<String> list = new ArrayList<>();
public void addElement(String element) {
list.add(element);
}
}
运行 javap
:
javac GenericExample.java
javap -s -p GenericExample
输出结果:
Compiled from "GenericExample.java"
public class GenericExample {
private java.util.List<java.lang.String> list;
public void addElement(java.lang.String);
descriptor: (Ljava/lang/String;)V
}
解释:
descriptor: (Ljava/lang/String;)V
:表示方法的签名,参数类型为java.lang.String
,返回类型为void
。
2.4 -l
选项:显示行号和本地变量表
这个选项会显示方法的行号和本地变量表,这对于调试非常有帮助。
示例代码:
public class LineNumberExample {
public static void main(String[] args) {
int a = 10;
int b = 20;
int sum = a + b;
System.out.println("Sum: " + sum);
}
}
运行 javap
:
javac LineNumberExample.java
javap -l -c LineNumberExample
输出结果:
Compiled from "LineNumberExample.java"
public class LineNumberExample {
public LineNumberExample();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: ldc #5 // String Sum:
20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: iload_3
24: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
27: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
30: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: return
LineNumberTable:
line 2: 0
line 3: 2
line 4: 4
line 5: 8
}
解释:
LineNumberTable
:显示了代码行号与字节码指令的对应关系。LocalVariableTable
:显示了方法中使用的局部变量及其范围。
2.5 -v
选项:显示详细信息
这个选项会显示类文件的详细信息,包括常量池、访问标志、字段和方法的详细信息等。
示例代码:
public class DetailedExample {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
运行 javap
:
javac DetailedExample.java
javap -v DetailedExample
输出结果:
Classfile /path/to/DetailedExample.class
Last modified Apr 28, 2025; size 502 bytes
MD5 checksum 1234567890abcdef1234567890abcdef
Compiled from "DetailedExample.java"
public class DetailedExample
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#17 // java/lang/Object."<init>":()V
#2 = Fieldref #18.#19 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #20 // Hello, World!
#4 = Methodref #21.#22 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #23 // DetailedExample
#6 = Class #24 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 SourceFile
#12 = Utf8 DetailedExample.java
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 StackMapTable
#16 = Class #25 // java/lang/StringBuilder
#17 = NameAndType #7:#8 // "<init>":()V
#18 = Class #26 // java/lang/System
#19 = NameAndType #27:#28 // out:Ljava/io/PrintStream;
#20 = Utf8 Hello, World!
#21 = Class #29 // java/io/PrintStream
#22 = NameAndType #30:#31 // println:(Ljava/lang/String;)V
#23 = Utf8 DetailedExample
#24 = Utf8 java/lang/Object
#25 = Utf8 java/lang/StringBuilder
#26 = Utf8 java/lang/System
#27 = Utf8 out
#28 = Utf8 Ljava/io/PrintStream;
#29 = Utf8 java/io/PrintStream
#30 = Utf8 println
#31 = Utf8 (Ljava/lang/String;)V
{
public DetailedExample();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, World!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 2: 0
}
SourceFile: "DetailedExample.java"
解释:
- 常量池(Constant Pool):包含了类文件中用到的所有常量,如字符串常量、类名、方法名等。
- 访问标志(Flags):如
ACC_PUBLIC
表示类是公共的,ACC_SUPER
表示类支持invokespecial
指令。 - 字段和方法的详细信息:包括方法的字节码、行号表、局部变量表等。
3. javap
的高级用法
3.1 显示内部类信息
如果类中定义了内部类,javap
也可以显示内部类的结构。
示例代码:
public class OuterClass {
private class InnerClass {
void display() {
System.out.println("Inner class method");
}
}
public void test() {
InnerClass inner = new InnerClass();
inner.display();
}
}
运行 javap
:
javac OuterClass.java
javap -p OuterClass
输出结果:
Compiled from "OuterClass.java"
public class OuterClass {
private class InnerClass {
void display();
}
public void test();
}
3.2 显示注解信息
如果类或方法上有注解,javap
也可以显示注解的详细信息。
示例代码:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {
String value();
}
@MyAnnotation("Hello")
public class AnnotatedClass {
public void display() {
System.out.println("Annotated method");
}
}
运行 javap
:
javac AnnotatedClass.java
javap -s -p AnnotatedClass
输出结果:
Compiled from "AnnotatedClass.java"
@MyAnnotation(value=Hello)
public class AnnotatedClass {
public AnnotatedClass();
public void display();
}
4. javap
的实际应用场景
4.1 调试字节码
当遇到复杂的性能问题或代码行为异常时,可以通过 javap
查看字节码,了解代码的实际执行逻辑。
示例:
public class LoopExample {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
运行 javap -c LoopExample
,查看循环的字节码实现。
4.2 学习 Java 虚拟机规范
通过 javap
查看字节码,可以更好地理解 Java 虚拟机的规范和字节码指令。
4.3 分析第三方库
对于开源的第三方库,可以通过 javap
查看其内部实现,帮助理解库的逻辑。
5. 总结
javap
是一个功能强大的工具,可以帮助开发者深入理解 Java 类文件的结构和字节码。通过掌握 javap
的各种选项和用法,可以更好地调试代码、优化性能、学习 JVM 规范。希望本文的介绍能帮助你更好地使用 javap
工具。