Skip to content

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, reference1 槽
long, double2 槽(连续两个槽)

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: 15

LocalVariableTable 属性

作用

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      I

LocalVariableTypeTable 属性

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 工具使用

基于 VitePress 构建