魔数
- 每个Class文件开头的4个字节的无符号整数称为魔数(Magic Number)
- 作用:确定这个文件是否为一个能被虚拟机接受的有效合法的文件,魔数是class文件的标识符
- 魔数固定值:0xCAFEBABE
Class文件版本
-
紧接着魔数的4个字节存储的是Class文件的版本号。同样也是4个字节
-
第5、6个字节所代表的含义:编译的副版本号minor_version
-
第7、8个字节:编译的主版本号major_version
-
共同构成了class文件的格式版本号,譬如某个Class文件的主版本号为M.副版本号为m,那么这个Class文件的格式,版本号就确定为:M.m
- Java的版本号是从45开始的,JDK 1.1之后的每个JDK大版本发布主版本号向上加1
- 不同版本的Java编译器编译的Class文件对应的版本是不一样的,目前,高版本的Java虚拟机可以执行由低版本编译器生成的Class文件
- 低版本的Java虚拟机不能执行由高版本编译器生成的Class文件,若尝试执行会出现错误:java.lang. UnsupportedClassVersionError异常(向下兼容)
- 实际应用中,由于开发环境和生产环境的不同.可能会导致该问题的发生,需要我们在开发时注意开发编译的JDK版本和生产环境中的JDK版本是否一致
- 虚拟机JDK版本为1.k(k>=2)时,对应的class文件格式版本号的范围为45~44+k.0(含两端)。
常量池
- 常量池是Class文件中内容最为丰富的区域之一
- 常量池对于Class文件中的字段和方法解析也有着至关重要的作用
- 随着Java虚拟机的不断发展,常量池的内容也日渐丰富,常量池是整个Class文件的基石
- 在版本号之后,紧跟着的是常量池的数量,以及若干个常量池表项.
- 常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的无符号数,代表常量池容量计数值(constant_pool_ count)
- 与Java中语言习惯不一样的是,这个容量计数是从1而不是0开始的
- Class文件使用了一个前置的容量计数器(constant_pool_ count)加若干个连续的数据项(constant_pool)的形式来描述常量池内容
- 把这一系列连续常量池数据称为常量池集合
- 常量池表项中,用于存放编译时期生成的各种字面员和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放
constant_pool_ count
- 常量池的数量不固定,时长时短,需要放置两个字节来表示常量池容量计数值
,常量池容量计数值(u2类型):从1开始,表示常量池中有多少项常量 - 把第0项常量空出来,用索引值0来表示不引用任何一个常量池项目
constant_pool
- 一种表结构,以1~constant_pool_ count-1为索引,表明后面有多少个常量项,常量池主要存放两大类常量:字面量(Literal)、符号引用(Symbolic References)
- 包含了Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量
- 常量池中的每一项都具备相同的特征.第1个字节作为类型标记,用于确定该项的格式,这个字节称为tag byte(标记字节、标签字节)
常量类型
类型 | 标志(或标识) | 描述 |
---|---|---|
CONSTANT_utf8_info | 1 | UTF-8编码的字符串 |
CONSTANT_Integer_info | 3 | 整型字面量 |
CONSTANT_Float_info | 4 | 浮点型字面量 |
CONSTANT_Long_info | 5 | 长整型字面量 |
CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
CONSTANT_Class_info | 7 | 类或接口的符号引用 |
CONSTANT_String_info | 8 | 字符串类型字面量 |
CONSTANT_Fieldref_info | 9 | 字段的符号引用 |
CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 标志方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
- 字面量:文本字符串
- 符号引用
- 声明为final的常量值
- 类、接口的全限定名
- 方法名称、描述符
- 字段名称、描述符
- 全限定名:包名的点换成斜杠,包名结尾用分号隔开
- 简单名称:没有类型修饰的方法、字段名称
描述符
- 用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)、返回值
- 根据描述符规则,基本数据类型(byte, char. double. float, int. long, short, boolean)、代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示
标志符 | 含义 |
---|---|
B | 基本数据类型byte |
C | 基本数据类型char |
D | 基本数据类型double |
F | 基本数据类型float |
I | 基本数据类型int |
J | 基本数据类型long |
S | 基本数据类型short |
Z | 基本数据类型boolean |
V | 代表void类型 |
L | 对象类型,比如:Ljava/lang/Object; |
[ | 数组类型,代表一维数组。比如:double[][][] 则为 [[[D |
- 用描述符来描述方法时,按照先参数列表,后返回值的顺序描述
- 参数列表按照参数的严格顺序放在一组小括号“()”之内
- java.lang.String toString()的描述符为()Ljava/lang/String;,方法int abc(int[] x, int y)的描述符为([II)I.
- 虚拟机在加载class文件时才会进有动态链接
- Class文件中不会保存各个方法和字段的最终内存布局信息,因此,这些字段和方法的符号引用不经过转换是无法直接被JVM使用的
- 当JVM运行时,需要从常量池中获得符号引用,然后在类加载的解析阶段将其替换为直接引用,并翻译到具体内存地址中
符号引用和直接引用
- 符号引用
- 以一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,只要使用时能无歧义定位到目标
- 与JVM实现的内存布局无关,引用的目标不一定已经加载到内存中
- 直接引用:
- 可以是直接指向目标的指针、相对偏移量、间接定位到目标的句柄
- 与JVM实现的内存布局相关
- 同一个符号引用在不同虚拟机实例翻译的直接引用一般不会相同
- 有了直接引用,说明引用的目标必定已经在内存中
常量类型和结构
- 标志为15、16、18的常量项类型是支持动态语言调用的(JDK7加入)
总结
- 表开始的第一位是u1类型标志位(tag),代表当前这个常量项使用的,是哪种表结构(常量类型)
- CONSTANT_Utf8_info常量项:一种使用改进过的UTF-8编码格式来存储诸如文字字符串、类或接口的全限定名、字段、方法的简单名称、描述符等常量字符串信息
- 14种常量项结构中有13个常量项占用的字节
- 只有CONSTANT_utf8_info。占用字节不固定,其存放的是字面量和符号引用,这些内容都会是一个字符串,这些字符串的大小是在编写程序时才确定,编译后,通过utf-8编码可以知道其长度
- 常量池:可以理解为class文件之中的资源仓库,它是class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用class文件空间最大的数据项目之一
- 需要常量池的原因
- Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态链接
- 也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址
- 也就无法直接被虚拟机使用,当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中