Code/LineNumberTable 等属性解析
Code 属性及其子属性详解
Code 属性是 Class 文件中最核心的属性,它存储了方法的字节码。Code 属性还包含多个子属性,用于调试和优化。
Code 属性完整结构
java
Code_attribute {
u2 attribute_name_index; // "Code"
u4 attribute_length; // 属性长度
u2 max_stack; // 操作数栈最大深度
u2 max_locals; // 局部变量表槽数
u4 code_length; // 字节码长度
u1 code[code_length]; // 字节码指令
u2 exception_table_length; // 异常表长度
exception_info exception_table[exception_table_length];
u2 attributes_count; // 子属性数量
attribute_info attributes[attributes_count]; // 子属性
}max_stack 和 max_locals
max_stack
max_stack 表示方法执行时操作数栈的最大深度。JVM 需要在方法入口预分配足够的栈空间:
java
public int calculate() {
int a = 1;
int b = 2;
int c = 3;
int d = (a + b) * c; // 这里同时需要 a+b 和 c,共 3 个值
return d;
}这个方法可能的最大栈深度为 3:
iload_1 // a 入栈(深度 1)
iload_2 // b 入栈(深度 2)
iadd // 弹出 b 和 a,结果入栈(深度 1)
iload_3 // c 入栈(深度 2)
imul // 弹出 c 和 a+b,结果入栈(深度 1)max_locals
max_locals 表示局部变量表的大小(以槽为单位):
java
public void method(int a, long b, double c, Object d) {
// args_size = 1 + 2 + 2 + 1 = 6
// a → slot 0
// b, b+1 → slot 1, 2(long 占两个槽)
// c, c+1 → slot 3, 4(double 占两个槽)
// d → slot 5
}槽的分配规则:
| 类型 | 槽数 |
|---|---|
byte, char, short, int, float, reference | 1 槽 |
long, double | 2 槽(连续两个槽) |
LineNumberTable 属性
作用
LineNumberTable 建立字节码偏移量与源码行号的对应关系:
字节码偏移 → 源码行号
0 → 第 10 行
5 → 第 12 行
10 → 第 14 行结构
java
LineNumberTable_attribute {
u2 attribute_name_index; // "LineNumberTable"
u4 attribute_length;
u2 line_number_table_length;
line_number_info {
u2 start_pc; // 字节码偏移
u2 line_number; // 源码行号
} line_number_table[line_number_table_length];
}调试信息影响
bash
# 编译时禁用 LineNumberTable
javac -g:none User.java
# 效果:
# 堆栈中看不到行号
# Exception in thread "main" java.lang.NullPointerException
# at User.main(User.java)
# ↑ 看不到具体行号,只知道在 User.java
# 正常编译(默认包含调试信息)
javac User.java
# 堆栈中有行号
# Exception in thread "main" java.lang.NullPointerException
# at User.getName(User.java:15)
# ↑
# 具体行号查看 LineNumberTable
bash
javap -l User.class
# 输出(部分):
# LineNumberTable:
# line 10: 0
# line 11: 5
# line 12: 10
# line 14: 15LocalVariableTable 属性
作用
LocalVariableTable 描述局部变量表中每个槽的变量名和类型:
java
public int add(int a, int b) {
return a + b;
}对应的 LocalVariableTable:
LocalVariableTable:
Start Length Slot Name Descriptor
0 5 0 this Lcom/example/User;
0 5 1 a I
0 5 2 b I结构
java
LocalVariableTable_attribute {
u2 attribute_name_index; // "LocalVariableTable"
u4 attribute_length;
u2 local_variable_table_length;
local_variable_info {
u2 start_pc; // 作用域起始 pc
u2 length; // 作用域长度
u2 name_index; // 变量名
u2 descriptor_index; // 描述符
u2 index; // 局部变量槽号
} local_variable_table[local_variable_table_length];
}调试信息影响
bash
# 编译时禁用 LocalVariableTable
javac -g:none User.java
# 影响:
# 1. 调试器看不到局部变量名(只能用 slot 编号)
# 2. 反射 API 中的 getParameters() 返回的 Parameter 对象
# 没有变量名(JDK 8+ 可用 -parameters 保留参数名)JDK 8+ 的参数名保留
bash
# 保留方法参数名(通过反射可访问)
javac -parameters User.java
# 编译后,LocalVariableTable 包含参数名
javap -v User.class | grep LocalVariableTable
# LocalVariableTable:
# Start Length Slot Name Descriptor
# 0 5 0 a I
# 0 5 1 b ILocalVariableTypeTable 属性
JDK 5 引入泛型后,LocalVariableTable 中的描述符是擦除后的类型。需要 LocalVariableTypeTable 来保存完整的泛型签名:
java
public <T extends Comparable> T findMax(List<T> list) {
T max = list.get(0);
return max;
}LocalVariableTable:
max: descriptor: Ljava/lang/Object; ← 擦除后的类型
LocalVariableTypeTable:
max: signature: Ljava/lang/Comparable; ← 泛型签名SourceFile 属性
记录源文件名:
java
// User.java
public class User { }SourceFile: "User.java"Deprecated 属性
标记已废弃的类/方法/字段:
java
@Deprecated
public void oldMethod() { }Deprecated: <no information>Synthetic 属性
标记编译器生成的非源码元素:
java
// 编译器生成
class Outer {
static class $1 { // 匿名内部类
final Outer this$0; // 编译器添加的外部类引用
}
}// 字节码中的标志
Synthetic: <no information>调试信息完整示例
bash
# 编译并查看所有属性
javap -v User.class完整输出:
public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 3: 0
line 4: 3
LocalVariableTable:
Start Length Slot Name Descriptor
0 5 0 this Lcom/example/User;
0 5 1 a I
0 5 2 b I本节小结
Code 子属性的核心要点:
| 属性 | 作用 | 调试时是否必需 |
|---|---|---|
| LineNumberTable | 字节码偏移 → 源码行号 | 影响堆栈行号 |
| LocalVariableTable | 局部变量槽 → 变量名/类型 | 影响调试器 |
| LocalVariableTypeTable | 泛型局部变量签名 | 影响泛型反射 |
| SourceFile | 源文件名 | 影响堆栈文件名 |
| Deprecated | 标记废弃 | 影响警告 |
| Synthetic | 编译器生成标记 | 影响调试 |
-g:none 可以禁用所有调试信息,减小 class 文件大小,但会失去调试和诊断能力。
下一节,我们来看 javac -g/javap 工具使用。
