Java-类文件结构
电脑只认识’0’和’1’,我们的程序经过编译器编译成0和1组成的二进制文件
的时候才能由计算机执行。虽然10多年过去了,电脑还是只能识别0和1,但现在可以把编译结果转为字节码
,是存储格式发展的一小步,却是编译语言发展的一大步。
class文件结构
Java语言”一次编译,到处运行
“,原理就是:源码文件并没有直接编译成机器指令,而是编译成Java虚拟机可以识别和运行的字节码.class文件
。字节码文件
,是一种平台无关的中间编译结果,由java虚拟机读取,解析和执行。
Class文件是一组以8位字节为基础单位的二进制流
,各数据项严格按顺序排列其中,中间没有添加任何分隔符。当遇到占用8位字节以上空间的数据项时,按照高位在前的方式分割成若干个8位字节进行存储
。根据《JAVA虚拟机规范》的规定,class文件格式采用一种类似C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号
数和表
。
- 无符号数
属于基本类型的数据,以u1
, u2
, u4
, u8
来分别代表1个字节,2个字节,4个字节和8个字节
的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码的字符串值。
- 表
由多个无符号数或其他表作为数据项构成的复合数据类型,所以表都习惯性地以“_info
“结尾。表用于描述有层次关系的复合结构数据,整个Class文件就是一张表。表由下图所示的数据项构成:
(图片来源于网络)
magic
:前1-4字节被称为魔数
,魔数值来唯一确定文件类型,java的Class文件魔数是:0xCAFEBABE。minor_version
和major_version
:5-6个字节代表次版本号,7-8个字节代表主版本号。constant_pool_count
:常量池的数目constant_pool
:常量池
,Class类文件中出现的第一个表类型数据,分为两种:
- 字面量:包括文本字符串、final类型常量值。
- 符号引用:包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
access_flags
:类或接口层面的访问控制
信息,通常存储的信息包括:Class类文件是类、接口、枚举或是注解;是否定义为public类型;是否定义为abstract类型;类是否被定义为final等等。this_class
:类索引用于确定类的全限定名super_class
:父类索引用于确定父类的全限定名interfaces
:接口索引用于确定接口的全限定名interfaces_count
:存储接口数量(可以实现多个接口)field_info
:描述接口或者类中声明的变量
,field
:包括了类级变量(静态变量)和实例级变量(成员变量),但不包括方法内部的局部变量fields_count
:类和实例变量总数method_info
:描述类或者接口中声明的方法
methods_count
:文件中方法总数method
:方法存储了方法的访问标识、是否静态、是否final、是否同步synchronized、是否本地方法native、是否抽象方法abstract、方法返回值类型、方法名称、方法参数列表等信息。attribute_info
:属性表
是Class文件格式中最具扩展性的一种数据项目,用于存放:field_info字段表、method_info方法表以及Class文件的专有信息,属性表不要求各个属性有严格顺序,只要求不与已有的属性名字重复即可。
属性表
中存放的常用信息如下:
属性
属性表
如上图所示,对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info
类型的常量表来表示,而属性值的结构则是完全自定义的,只要说明属性值所占用的位数长度即可。
Code属性
方法体中的字节码指令存储在Code属性中
,code属性是Class文件最重要的一个属性。max_stack
:代表操作栈深度的最大值
,在方法执行的任何时刻,操作数栈都不会超过这个深度,虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度。max_locals
:代表局部变量表所需的存储空间,单位为slot,对于byte,char,float,int,short,boolean,reference和returnAddress每个局部变量占用一个slot,而double和long需要两个slot.
- 并不是方法中用到了多少个局部变量,就把这些局部变量所占的Slot之和作为max_locals的值,原因是局部变量表中的slot可以重用,当代码执行超过一个局部变量的作用域时,这个局部变量所占用的slot就可以被其他局部变量所使用。这个值编译器会自动计算得出。
code_length
和code
:用来存储java源程序编译后生成的字节码指令,code_length代表字节码长度,code用于存储字节码指令的一系列字节流。字节码的每个指令就是一个字节。这样可以推出,一个字节最多可以代表256条指令,目前已经使用了约200条。
- 而code_length有4个字节,所以一个方法做多允许有65535条字节码指令,如果超过这个限制,javac就会拒绝编译,一般JSP可能会这个原因导致失败。
在任何实例方法中,都可以通过this关键字访问到此方法所属的对象,它的底层实现就是通过javac编译器在编译的时候把this关键字的访问转变为对一个普通方法参数的访问。
因此,任何实例方法的参数Args_size最少是1,而且locals最少也是1.而静态方法就可以为0了。
异常表:实际是Java代码的一部分,从start_pc行到end_pc行出现了类型为catch_type的异常,就转到第handler_pc行处理。这四个参数就组成了异常表。
对于finally的实现,实际上就是对catch字段和前面对于任意情况都运行的异常表记录
Exceptions属性
表示方法可能抛出number_of_exceptions种受查异常,每种受查异常使用一个exception_index_table项表示
LineNumberTable属性
用于描述java源代码行号与字节码行号直接的对应关系
。可以用-g:none
或-g:lines
选项来取消或要求生成这项信息,如果取消主要影响就是当抛出异常时,不会显示出错的行号,调试时无法设置断点。我们可以使用“javap -l class文件”来查看:
eg :
1 | package com.xxo.demo.util; |
LocalVariableTable属性
用于描述栈帧中局部变量表中的变量与源码中定义的变量之间的关系
。也可以选择开关,关闭后果就是报错时看不到变量名称。
SourceFile属性
用于记录生成这个Class文件的源码文件名称
。可选,sourcefile_index数据项是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源文件的文件名
ConstantValue属性
通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量才可以使用这项属性。
- eg:int x=123;和static int x=123;的区别在于,
- 非静态变量(实例变量)的赋值是在
实例构造器<init>
方法中进行的; - 而静态变量,则有两种方式可以选择:
- 在类构造器
方法中进行, - 使用ConstantValue属性来赋值。
目前Sun Java编译器的选择是:如果同时使用final和static来修饰一个变量,并且这个变量的数据类型是基本类型或者String的话,就生成ConstantValue属性来进行初始化。如果这个变量没有用final修饰,或者非以上类型,则选择在
InnerClasses属性
用于记录内部类与宿主类之间的关系
,如果一个类中定义了内部类,那编译器将会为它以及他所包含的内部类生成InnerClasses属性。
Deprecated和synthetic属性
都属于标志类布尔属性
,只存在有和没有的区别,没有属性值的概念。
- deprecated:属性用于表示某个类,字段或者方法,已经被程序作者定为不再推荐使用,可以在代码中使用@deprecated注释进行设置。
- synthetic:表示字段或方法不是java源码产生,而是编译器自行添加的。