尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

JVM规范第 4 章:class 文件格式

JVM规范第 4 章:class 文件格式 基于 Oracle 官方《The Java Virtual Machine Specification》第 4 章Java SE 26编写。这是什么为什么值得懂读懂class文件格式你就能看穿javap -v反汇编输出的每一行究竟对应文件里的哪个字节理解「为什么long/double常量在常量池里占两项」这类反直觉规则在排查诡异问题如版本不兼容、桥方法、泛型擦除时直达根源真正明白「JVM 与语言无关」是如何从文件层面落地的。本章是规范性内容与第 3 章的非规范性示例不同它精确规定了每个字节的含义。1. 文件的物理形态class文件是一串8 位字节流整体大端序big-endian高位在前。规范定义了三个基础数据类型类型含义u11 字节无符号量u22 字节无符号量u44 字节无符号量文件用类 C 的伪结构描述结构里的成员称为item连续存放、无填充对齐不像 C 结构体那样有 padding。唯一一个第 4 章之外的规定多字节数据一律大端序。这意味着不同硬件架构的机器读到的是完全一致的字节这是「一次编译、到处运行」在文件层面的保证。2. 整体结构ClassFile单个class文件只描述一个类、接口或模块整体结构如下ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }注意几个要点magic固定为0xCAFEBABE。这是 JVM 校验文件合法性的第一关——读到不是这个魔数的文件会直接拒绝著名的「咖啡宝贝」梗。constant_pool_count是项数 1有效的常量池索引范围是0 index constant_pool_count。第 0 项被保留例如让「不指向任何常量」的字段能存 0。所有xxx_count后都紧跟一个xxx[]数组长度即前面那个计数。我会用「咖啡宝贝」帮你记住魔数CA FE BA BE→CafeBabe。3. 版本号minor_version/major_versionJVM 接受的主版本号范围45 ~ 70Java SE 26 支持到 70。当major_version ≥ 56即 Java 12时minor_version必须为 0 或 65535minor 0正式发布版minor 65535预览Preview版本把 class 锁死在单一发布版不能跨版加载。版本70.65535表示「依赖 Java SE 26 的预览特性」。实战提示当你看到UnsupportedClassVersionError: ... major version 65意思是这份 class 用 JDK 21major 65编译但你的运行环境低于它。对照表见jvm-intro-tutorial.md第 1 章。4. 常量池The Constant Pool常量池是class文件的「中央字典」几乎所有其他部分都通过索引引用它。它是变长表的数组灵活性最高。4.1 通用格式cp_info { u1 tag; u1 info[]; }tag决定后面info的结构。共17 种常量常量种类Tag说明CONSTANT_Utf81修改 UTF-8 字符串名字、描述符等CONSTANT_Integer3int常量CONSTANT_Float4float常量CONSTANT_Long5long常量占两项CONSTANT_Double6double常量占两项CONSTANT_Class7类/接口的符号引用CONSTANT_String8String字面量引用CONSTANT_Fieldref9字段的符号引用CONSTANT_Methodref10方法的符号引用CONSTANT_InterfaceMethodref11接口方法的符号引用CONSTANT_NameAndType12名称 描述符对CONSTANT_MethodHandle15方法句柄CONSTANT_MethodType16方法类型CONSTANT_Dynamic17动态常量引导方法CONSTANT_InvokeDynamic18invokedynamic引导CONSTANT_Module19模块名仅模块文件CONSTANT_Package20包名仅模块文件4.2 几个最关键的结构// 类/接口引用CONSTANT_Class_info{u1 tag;// 7u2 name_index;// 指向 CONSTANT_Utf8内部名如 java/lang/Object}// 字段/方法引用CONSTANT_Fieldref_info{u1 tag;// 9 / 10 / 11u2 class_index;// 指向 CONSTANT_Classu2 name_and_type_index;// 指向 CONSTANT_NameAndType}// 名称 描述符对CONSTANT_NameAndType_info{u1 tag;// 12u2 name_index;// Utf8字段名或方法名u2 descriptor_index;// Utf8字段/方法描述符}// 字符串字面量CONSTANT_String_info{u1 tag;// 8u2 string_index;// 指向 CONSTANT_Utf8}// 整数 / 浮点CONSTANT_Integer_info{u1 tag;u4 bytes;}// 3CONSTANT_Float_info{u1 tag;u4 bytes;}// 4IEEE 754// 长整 / 双精占两个槽位CONSTANT_Long_info{u1 tag;u4 high_bytes;u4 low_bytes;}// 5CONSTANT_Double_info{u1 tag;u4 high_bytes;u4 low_bytes;}// 6反直觉点Long和Double在常量池里占连续两项index和index1且index1不可用。原因是 64 位值无法塞进单个u4。修改 UTF-8Modified UTF-8CONSTANT_Utf8用的是 JVM 自己定制的 UTF-8 变体——\u0000被编码成两字节的0xC0 0x80保证字符串里绝不出现裸0x00方便 C 风格处理并只使用 1/2/3 字节形式。5. 访问标志access_flags紧跟在常量池之后this_class之前。它是一组位掩码含义取决于当前是类、接口还是模块表 4.1-B标志值含义ACC_PUBLIC0x0001声明public包外可见ACC_FINAL0x0010final无子类ACC_SUPER0x0020让invokespecial按新版语义处理父类方法现代编译器必置ACC_INTERFACE0x0200是接口而非类ACC_ABSTRACT0x0400abstract不能实例化ACC_SYNTHETIC0x1000编译器生成源码中无对应声明ACC_ANNOTATION0x2000是注解接口ACC_ENUM0x4000是enum类ACC_MODULE0x8000是模块module-info而非类/接口若置了ACC_MODULEthis_class必须指向module-infosuper_class、interfaces_count、fields_count、methods_count全为 0且必须含一个Module属性。6. 类、父类、接口索引u2 this_class;// 指向 CONSTANT_Class本类u2 super_class;// 0 表示没有父类仅 java/lang/Object 如此否则指向直接父类u2 interfaces_count;u2 interfaces[interfaces_count];// 每个元素指向 CONSTANT_Class直接超接口super_class普通类要么指向java/lang/Object要么为 0接口必须指向Object。接口数组存的是「直接超接口」不含间接继承的。7. 字段表Fieldsfields仅包含本类型直接声明的字段不含继承的。结构field_info{u2 access_flags;u2 name_index;// - Utf8字段名u2 descriptor_index;// - Utf8字段描述符见第 9 节u2 attributes_count;attribute_info attributes[attributes_count];}字段访问标志表 4.5-A与类标志是不同的一张表标志值含义ACC_PUBLIC0x0001publicACC_PRIVATE0x0002privateACC_PROTECTED0x0004protectedACC_STATIC0x0008staticACC_FINAL0x0010finalACC_VOLATILE0x0040volatileACC_TRANSIENT0x0080transient序列化跳过ACC_SYNTHETIC0x1000编译器生成ACC_ENUM0x4000enum 元素接口里的字段必须是ACC_PUBLIC | ACC_STATIC | ACC_FINAL即public static final常量。8. 方法表Methodsmethod_info{u2 access_flags;u2 name_index;// - Utf8方法名u2 descriptor_index;// - Utf8方法描述符u2 attributes_count;attribute_info attributes[attributes_count];}方法访问标志表 4.6-A标志值含义ACC_PUBLIC0x0001publicACC_PRIVATE0x0002privateACC_PROTECTED0x0004protectedACC_STATIC0x0008staticACC_FINAL0x0010finalACC_SYNCHRONIZED0x0020synchronizedACC_BRIDGE0x0040桥方法泛型/协变返回生成ACC_VARARGS0x0080变长参数ACC_NATIVE0x0100nativeACC_ABSTRACT0x0400abstractACC_STRICT0x0800strictfp仅 major 46~60ACC_SYNTHETIC0x1000编译器生成特殊方法名init实例构造器clinit类/接口初始化器static {}块必须是staticmajor ≥ 51。方法真正的字节码并不在这里而藏在它自己的Code属性里见第 10 节。9. 描述符Descriptors描述符是用字符串编码「类型」的紧凑约定避免重复存储完整类型名。字段描述符FieldType → BaseType | ClassType | ArrayTypeBaseType对应类型BbyteCcharDdoubleFfloatIintJlongSshortZboolean引用类型L ClassName ;内部名.换成/例如Ljava/lang/Object;数组[前缀例如[[[D表示double[][][]最多 255 维方法描述符MethodDescriptor: ( {ParameterDescriptor} ) ReturnDescriptor ReturnDescriptor: FieldType | V // V 表示 void参数描述符依次列出不含this参数总「字数」wordlong/double 计 2其余计 1≤ 255。示例Object m(int i, double d, Thread t)→(IDLjava/lang/Thread;)Ljava/lang/Object;10. 属性Attributes属性是class文件的「可扩展插槽」——你能在类、字段、方法、Code 内部多处挂载属性。通用格式attribute_info{u2 attribute_name_index;// - Utf8属性名u4 attribute_length;u1 info[attribute_length];}第 4 章预定义了约 30 个属性按编号摘选关键的属性编号挂载位置作用ConstantValue4.7.2字段常量字段的初始值如static final intCode4.7.3方法方法字节码、异常表、局部变量槽数等StackMapTable4.7.4Code类型校验所需的栈映射Exceptions4.7.5方法受检异常列表InnerClasses4.7.6类内部类关系EnclosingMethod4.7.7类局部/匿名类的外围方法Synthetic4.7.8类/字段/方法标记编译器生成Signature4.7.9类/字段/方法泛型签名擦除后保留SourceFile4.7.10类源文件名LineNumberTable4.7.12Code字节码偏移 ↔ 源码行号LocalVariableTable4.7.13Code局部变量名与槽位Deprecated4.7.15类/字段/方法标记已废弃10.1Code属性最值得懂方法体就藏在这里。结构如下Code_attribute{u2 attribute_name_index;u4 attribute_length;u2 max_stack;// 操作数栈最大深度u2 max_locals;// 局部变量表所需槽数long/double 占 2u4 code_length;u1 code[code_length];// 真正的字节码指令u2 exception_table_length;{u2 start_pc;u2 end_pc;u2 handler_pc;u2 catch_type;}exception_table[exception_table_length];u2 attributes_count;attribute_info attributes[attributes_count];// 常含 LineNumberTable 等}max_stack/max_localsJVM 加载时就知道要分配多少栈和局部变量空间exception_table就是第 3 章讲过的「try-catch靠异常表而非跳转指令」的文件级实现init/clinit等方法的字节码都在此。10.2ConstantValue属性字段常量ConstantValue_attribute{u2 attribute_name_index;u4 attribute_length;// 恒为 2u2 constantvalue_index;// 指向常量池里的常量}只有同时是static final的基本类型或String字段才用它例如publicstaticfinalintMAX100;编译后MAX的field_info会带ACC_PUBLIC | ACC_STATIC | ACC_FINAL并挂一个ConstantValue直接指向常量池里CONSTANT_Integer(100)。11. 名字的约定二进制名Binary Name内部形式用/代替.例如java/lang/Thread存于CONSTANT_Utf8。非限定名Unqualified Name字段/方法名不含. ; [ /方法名除init/clinit外也不含 。模块/包名存于CONSTANT_Module_info/CONSTANT_Package_info模块文件专用。12. 动手用javap -v拆解真实 class理论太抽象直接拆一个真实文件。先准备源码// Demo.javapublicclassDemo{publicstaticfinalintMAX100;privateintcount;publicintinc(){returncount1;}}编译并反汇编详情见jvm-compilation-tutorial.md第 3 章javac Demo.java javap-vDemo.class你会看到输出层层对应本章结构Classfile Demo.class Last modified ... minor version: 0 major version: 65 // 对照表JDK 21 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #2 // Demo super_class: #3 // java/lang/Object interfaces: 0 Constant pool: // 第 4 节常量池 #1 Methodref #3.#4 // java/lang/Object.init:()V #2 Class #5 // Demo #3 Class #6 // java/lang/Object ... { public static final int MAX; descriptor: I flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 100 // 第 10.2 节 public Demo(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: // 第 10.1 节 stack1, locals1, args_size1 0: aload_0 1: invokespecial #1 // Method java/lang/Object.init:()V 4: return public int inc(); ... }每一行都能在第 4 章找到出处——这就是「规范即地图」的感觉。小结整体magic 版本 常量池 类/父类/接口 字段 方法 属性常量池是中央字典17 种常量64 位值占双槽描述符用紧凑字符串编码类型是贯穿全章的「类型语言」属性是扩展机制Code存字节码、ConstantValue存常量、SourceFile/LineNumberTable辅助调试。
返回列表