news 2026/8/27 11:37:10

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 BECafeBabe。


3. 版本号:minor_version/major_version

  • JVM 接受的主版本号范围:45 ~ 70(Java SE 26 支持到 70)。
  • major_version ≥ 56(即 Java 12+)时,minor_version必须为 0 或 65535
    • minor = 0:正式发布版;
    • minor = 65535:预览(Preview)版本,把 class 锁死在单一发布版,不能跨版加载。
  • 版本70.65535表示「依赖 Java SE 26 的预览特性」。

实战提示:当你看到UnsupportedClassVersionError: ... major version 65,意思是这份 class 用 JDK 21(major 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;}// 4(IEEE 754)// 长整 / 双精(占两个槽位!)CONSTANT_Long_info{u1 tag;u4 high_bytes;u4 low_bytes;}// 5CONSTANT_Double_info{u1 tag;u4 high_bytes;u4 low_bytes;}// 6

反直觉点LongDouble在常量池里占连续两项indexindex+1),且index+1不可用。原因是 64 位值无法塞进单个u4

修改 UTF-8(Modified UTF-8)CONSTANT_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_SUPER0x0020invokespecial按新版语义处理父类方法(现代编译器必置)
ACC_INTERFACE0x0200是接口而非类
ACC_ABSTRACT0x0400abstract,不能实例化
ACC_SYNTHETIC0x1000编译器生成,源码中无对应声明
ACC_ANNOTATION0x2000是注解接口
ACC_ENUM0x4000enum
ACC_MODULE0x8000是模块(module-info)而非类/接口

若置了ACC_MODULEthis_class必须指向module-infosuper_classinterfaces_countfields_countmethods_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. 字段表(Fields)

fields仅包含本类型直接声明的字段(不含继承的)。结构:

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_PUBLIC0x0001public
ACC_PRIVATE0x0002private
ACC_PROTECTED0x0004protected
ACC_STATIC0x0008static
ACC_FINAL0x0010final
ACC_VOLATILE0x0040volatile
ACC_TRANSIENT0x0080transient(序列化跳过)
ACC_SYNTHETIC0x1000编译器生成
ACC_ENUM0x4000enum 元素

接口里的字段必须ACC_PUBLIC | ACC_STATIC | ACC_FINAL(即public static final常量)。


8. 方法表(Methods)

method_info{u2 access_flags;u2 name_index;// -> Utf8:方法名u2 descriptor_index;// -> Utf8:方法描述符u2 attributes_count;attribute_info attributes[attributes_count];}

方法访问标志(表 4.6-A):

标志含义
ACC_PUBLIC0x0001public
ACC_PRIVATE0x0002private
ACC_PROTECTED0x0004protected
ACC_STATIC0x0008static
ACC_FINAL0x0010final
ACC_SYNCHRONIZED0x0020synchronized
ACC_BRIDGE0x0040桥方法(泛型/协变返回生成)
ACC_VARARGS0x0080变长参数
ACC_NATIVE0x0100native
ACC_ABSTRACT0x0400abstract
ACC_STRICT0x0800strictfp(仅 major 46~60)
ACC_SYNTHETIC0x1000编译器生成

特殊方法名:

  • <init>:实例构造器;
  • <clinit>:类/接口初始化器(static {}块),必须是static(major ≥ 51)。

方法真正的字节码并不在这里,而藏在它自己的Code属性里(见第 10 节)。


9. 描述符(Descriptors)

描述符是用字符串编码「类型」的紧凑约定,避免重复存储完整类型名。

字段描述符

FieldType → BaseType | ClassType | ArrayType
BaseType对应类型
Bbyte
Cchar
Ddouble
Ffloat
Iint
Jlong
Sshort
Zboolean
  • 引用类型:L ClassName ;(内部名,.换成/),例如Ljava/lang/Object;
  • 数组:[前缀,例如[[[D表示double[][][](最多 255 维)

方法描述符

MethodDescriptor: ( {ParameterDescriptor} ) ReturnDescriptor ReturnDescriptor: FieldType | V // V 表示 void
  • 参数描述符依次列出,不含this
  • 参数总「字数」(word,long/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 int
Code4.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 占 2)u4 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_locals:JVM 加载时就知道要分配多少栈和局部变量空间;
  • exception_table:就是第 3 章讲过的「try-catch靠异常表而非跳转指令」的文件级实现;
  • <init>/<clinit>等方法的字节码都在此。

10.2ConstantValue属性(字段常量)

ConstantValue_attribute{u2 attribute_name_index;u4 attribute_length;// 恒为 2u2 constantvalue_index;// 指向常量池里的常量}

只有同时是static final的基本类型或String字段才用它,例如:

publicstaticfinalintMAX=100;

编译后MAXfield_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{publicstaticfinalintMAX=100;privateintcount;publicintinc(){returncount+1;}}

编译并反汇编(详情见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 节 stack=1, locals=1, args_size=1 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辅助调试。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 11:36:53

单芯片车载BMS方案:从架构设计到实测避坑指南

先交代一下背景&#xff1a;这几年做车载电池管理系统&#xff08;BMS&#xff09;的项目不在少数&#xff0c;但真正把方案从“AFE MCU 通讯芯片”这种分立式架构压缩到单芯片时&#xff0c;踩过的坑比想象中多。标题里的“Single-Chip Battery Management System for Cars”…

作者头像 李华
网站建设 2026/8/27 11:36:38

i.MX8系列Linux驱动开发与SBC适配:从选型到实战

干嵌入式 Linux 这些年&#xff0c;i.MX8、i.MX8M、i.MX8X 这三个名字几乎每天都要打交道。最近做了一个“Linux-Driven Modules and SBC Tap i.MX8, i.MX8M and iMX8X”方向的项目&#xff0c;说白了就是基于这三款 NXP 处理器做 Linux 驱动的硬件模块&#xff0c;并落到单板计…

作者头像 李华
网站建设 2026/8/27 11:36:05

单片机毕设项目:基于 STM32 的环境参数阈值自定义声光预警系统设计 基于 STM32 的多点气象要素实时采集终端设计(010605)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/27 11:34:16

HuggingFace NLP工程化实战:从模型调用到微调部署的完整路线

HuggingFace 是大模型和 NLP 工程化过程中绕不开的工具库。很多人误以为只要会用 pipeline(sentiment-analysis) 就学会了 HuggingFace&#xff0c;但真正进入模型微调和部署阶段时&#xff0c;往往卡在 tokenizer、Trainer、模型保存、显存管理和服务化这几个环节。这篇文章…

作者头像 李华
网站建设 2026/8/27 11:34:07

Agent 工具误调用的工程化治理:从 Demo 到生产级的三层防御体系

Agent 工具误调用的工程化治理&#xff1a;从 Demo 到生产级的三层防御体系 一、背景与问题定义 在大模型 Agent 应用开发中&#xff0c;“工具调用”&#xff08;Tool Calling / Function Calling&#xff09;是实现智能体与环境交互的核心能力。然而&#xff0c;当 Agent 从 …

作者头像 李华
网站建设 2026/8/27 11:32:01

ncmdumpGUI ncm 转 mp3 完整指南:三步离线批量解密网易云音乐文件

ncmdumpGUI ncm 转 mp3 完整指南&#xff1a;三步离线批量解密网易云音乐文件 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换&#xff0c;Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI ncmdumpGUI 是一款免费的 Window…

作者头像 李华