编译成字节码(.class 文件)
使用 javac
命令将源代码编译为 Java 字节码(bytecode)
它不是机器码,而是 JVM 能理解的中间语言(字节码),具有平台无关性。
编译过程简要:
- 词法分析:将字符序列转换为标记(token)。
- 语法分析:构建抽象语法树(AST)。
- 语义分析:检查类型、变量是否合法。
- 生成字节码:将 AST 转换为
.class
文件中的字节码指令。
类加载(Class Loading)
当你运行程序时,JVM 启动并开始加载所需的类:
JVM 使用 类加载器(ClassLoader) 来加载 .class
文件。类加载包括以下步骤:
- 加载(Loading):查找并导入类的
.class
文件。 - 验证(Verification):确保字节码是安全且符合规范的。
- 准备(Preparation):为类变量分配内存,并设置默认初始值。
- 解析(Resolution)(可选延迟):将符号引用替换为直接引用。
- 初始化(Initialization):执行类构造器
<clinit>
方法(静态变量赋值和静态代码块)。
执行字节码(Execution Engine)
Java 字节码由 JVM 的执行引擎来执行。具体方式有三种:
1. 解释执行(Interpretation)
JVM 逐条解释字节码并执行,速度较慢但跨平台兼容性强。
2. 即时编译(JIT Compilation)
热点代码(频繁执行的代码)会被 JIT 编译器动态编译为本地机器码,提升性能。
3. 本地方法调用(Native Method)
某些操作(如 I/O、线程)通过调用操作系统原生库实现。
垃圾回收(Garbage Collection)
在程序运行期间,JVM 自动管理内存,GC 会定期回收不再使用的对象,释放内存资源。
程序终止
当 main()
方法执行完毕,或者遇到异常、系统退出等,JVM 关闭,程序结束。
总结流程图
Java源代码 (.java)
↓ 编译 (javac)
字节码文件 (.class)
↓ 运行 (java)
类加载 → 验证 → 准备 → 解析 → 初始化
↓
执行引擎(解释 + JIT 编译)
↓
垃圾回收 & 程序终止
.class 文件整体结构(以字节为单位)
项 | 类型 | 数量 | 描述 |
---|---|---|---|
魔数(magic) | u4 | 1 | 固定值 0xCAFEBABE ,标识这是 Java class 文件 |
次版本号(minor_version) | u2 | 1 | 次版本号 |
主版本号(major_version) | u2 | 1 | 主版本号,如 52 表示 JDK 8 |
常量池计数器(constant_pool_count) | u2 | 1 | 常量池项数量(从 1 开始编号) |
常量池(constant_pool) | cp_info[] | constant_pool_count - 1 | 存储各种常量(字符串、类名、字段名等) |
访问标志(access_flags) | u2 | 1 | 类的访问权限(public、abstract 等) |
类索引(this_class) | u2 | 1 | 当前类的常量池索引 |
超类索引(super_class) | u2 | 1 | 父类的常量池索引 |
接口计数器(interfaces_count) | u2 | 1 | 实现的接口数量 |
接口表(interfaces) | u2[] | interfaces_count | 接口的常量池索引数组 |
字段计数器(fields_count) | u2 | 1 | 字段数量 |
字段表(fields) | field_info[] | fields_count | 字段的详细信息 |
方法计数器(methods_count) | u2 | 1 | 方法数量 |
方法表(methods) | method_info[] | methods_count | 方法的详细信息 |
属性计数器(attributes_count) | u2 | 1 | 属性数量 |
属性表(attributes) | attribute_info[] | attributes_count | 属性信息(如 SourceFile、Code 等) |
各部分详解
1. 魔数(Magic Number)
- 固定值:
0xCAFEBABE
- 用于识别是否是合法的 class 文件。
2. 版本号(Version)
minor_version
+major_version
- 常见主版本号对应:
- JDK 1.1: 45
- JDK 1.2: 46
- ...
- JDK 8: 52
- JDK 17: 61
3. 常量池(Constant Pool)
- 是
.class
文件中最复杂也最重要的结构之一。 - 包含多种类型的常量,例如:
- 类名、字段名、方法名
- 字符串常量
- 方法引用、字段引用
- UTF-8 字符串等
常量池中的每一项都有一个 tag 字段表示类型,比如:
CONSTANT_Utf8
(1)CONSTANT_Class
(7)CONSTANT_Fieldref
(9)CONSTANT_Methodref
(10)- ...
4. 访问标志(Access Flags)
- 标识该类是 public、final、interface、abstract 等。
5. 类索引、超类索引、接口表
- 通过常量池索引来引用当前类、父类和实现的接口。
6. 字段表(Fields)
- 描述类中声明的变量(包括静态变量、实例变量等)。
- 每个字段包含:
- 名称索引
- 描述符索引(描述字段类型)
- 属性表(如 ConstantValue、Synthetic 等)
7. 方法表(Methods)
- 描述类中声明的方法。
- 每个方法包含:
- 名称索引
- 描述符索引(参数和返回值类型)
- 属性表(如 Code、Exceptions、LineNumberTable 等)
方法体(Code 属性)
- 在方法的属性表中,
Code
属性是最核心的部分,它包含:- 操作数栈最大深度
- 局部变量表大小
- 字节码指令(bytecodes)
- 异常处理表
- 其他调试信息(如行号表)
8. 属性表(Attributes)
- 用于扩展 class 文件的附加信息。
- 常见属性有:
Code
:方法的字节码SourceFile
:记录源文件名LineNumberTable
:源码与字节码的行号映射LocalVariableTable
:局部变量表信息Exceptions
:方法抛出的异常列表BootstrapMethods
:用于动态语言支持(如 Lambda 表达式)
示例解析(简单 Hello.class)
假设我们有如下 Java 源码:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
编译后生成的 Hello.class
文件结构大致如下:
magic: CAFE BABE
minor_version: 0
major_version: 52 (JDK 8)
constant_pool: [各种符号引用]
access_flags: 0x0031 (public final super)
this_class: #5 => "Hello"
super_class: #6 => "java/lang/Object"
interfaces: none
fields: none
methods:
- main: descriptor ([Ljava/lang/String;)V, flags: public static
attributes:
- Code:
max_stack: 2
max_locals: 1
code_length: 10
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
exception_table: empty
attributes: LineNumberTable, LocalVariableTable
查看 .class 文件内容的工具
你可以使用以下工具来查看 .class
文件的内容:
1. javap
反汇编工具(JDK 自带)
javap -c -verbose Hello.class
-c
:显示字节码指令-verbose
:显示更详细的结构信息(包括常量池、属性等)
2. 二进制编辑器(如 Hex Editor)
- 可查看原始的十六进制数据。
- 如:WinHex、HxD、xxd 等。
3. ASM、ByteBuddy、Javassist 等字节码操作库
- 可用于分析、修改
.class
文件内容,常用于 AOP、性能监控、热修复等高级场景。