摘抄: 陈树义 Java技术精选
-各个语言的编译器吧文件编译成字节码,jvm把字节码解释给操作系统,或者是编译成机器代码
class类文件结构(一个自己代表两个字节码)
- class 文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在class文件中。
- 当遇到需要占用8位字节以上空间的数据项时候,会按照高在前的方式分割成若干个8位字节进行存储。
- 在字节码结构中,有两种最基本的数据类型来表示字节码文件格式,分别是:无符号数和表。
无符号数属于最基本的数据类型
- 1.它以 u1、u2、u4、u8 六七分别代表 1 个字节、2 个字节、4 个字节、8 个字节的无符号数.
- 2.可以用来描述数字、索引引用,数量值或者按照UTF-8编码构成的字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型
- 1.所有表都习惯性地以_info结尾。表用于描述有层次关系的复合结构的数据
比如u4 代表四个字节 可以用来标识java的魔数
比如cp_info标识常量池
上图就是我们一个类文件包含的东西
- 魔数与Class文件版本
- 常量池
- 访问标志
- 类索引、父类索引、接口索引
- 字段表集合
- 方法表集合
- 属性表集合
我们一般要描述同一类型 但是数量不定的多个数据的时候,经常会使用一个前置容量计数器加若干个连续的数据项形式,比如常量池个数+常量池
上图的数据之前顺序是严格的不能出现混乱
魔数与class文件的版本
- 魔数:确定这个文件是否能被虚拟机接受的class文件。
- 次版本号和主版本号,分别是5,6字节和7,8字节。注意高版本的jdk能处理低版本的字节文件,但是不能处理高于jdk版本的字节文件
常量池
常量池入口处存放一个u2类型,标识常量池中的常量数量。
此处容量技术是从1开始的
常量池值存放字面量(字符串或final的常量值)和符号引用
符号引用:类和接口的全限定名称,字段的名称和描述符,方法的名称和描述符
常量池的常量都是由cp_info这种表结构组成的,而且表结构不同其大小也不同。在 Java 虚拟机规范中一共有 14 种 cp_info 类型的表结构。
-
当虚拟机运行时,需要从常量池获得对应的符号引用,再类创建时或运行时进行解析,并得到具体的内存地址。
常量池的表.jpg -
14种cp_info类型的表结构如下图
表结构.jpg 根据表的类型我们可知道,常量池中还存有符号引用(类似于占位符,等到具体的对象地址形成后替换)
根据表的结构可知道各个常量的最大长度值是u2即65535
访问标识
-
这个class 是接口还是类,是什么访问类型,是否问abstract,是否是被final声明的类 等待
访问标识.jpg
类索引,父类索引与接口索引集合
- 类索引和父类索引都是一个u2类型的数据,因为java只能单继承
- 接口是索引是一组u2类型集合,这是因为接口可以实现多个。
- 类索引确定这个类的全限定名,父类索引用于确定父类的全限定名
- 所有类除了object 都有父索引
字段表集合
- 用于描述接口或类中声明的变量
- 描述符可以描述字段的数据类型
- 这些字段仅仅包括类级和实例级别变量,我们在方法内部声明的不算
- 方法表中的每个方法都用一个 method_info 表示,其数据结构如下:
- 因为字段名字和字段类型,是无法使用修饰符来表示,所以只能引用常量池中的常量来描述,即通过name_index和descriptor_index都是常量池的引用
-
字段表中也有属性表
微信截图_20181120230510.png
方法表集合
方法描述符要复杂,描述符用来描述方法的参数列表(数量,类型以及顺序)和返回值。
基本类型和无返回值的类型都用大写字符来表示(比如整数就是INT,无返回值就是V),而对象类型则用字符L加对象的全限定名(Ljava.lang.object)。数组就是在前面加个[整形数组就是[I-
描述描述方法时,先参数列表后返回值,参数列表按照参数的严格顺序放在一组小括号之内,如方法void inc()的描述符()V,如果是int indexof(int a)=(I)I
微信截图_20181120233315.png
包含描述方法字节码的code属性表
方法表中也有属性表
<clinit>代表类构造器,<init>代表实例构造器。前者会执行我们static{},类变量(父类的<clinit>会由该类被加载的时候自己调用,jvm会确保父类的<clinit>比子类<clinit>的调用造)。后者会执行{},实例变量和父类的实例构造器。
我们在类里面重载一个方法除了方法名称一样外,必须确保参数不同,如果参数相同但是返回值不同,则无法通过编译。但是如果我们绕过验证把他们编译成class文件,虽然返回值不同 但是也可以合法共存在同一个class文件中
属性表集合
- class文件,字段表,方法表都可以携带自己的属性表集合
-
jvm 规定下列几个属性
微信截图_20181120234904.png - 对于每个属性,他的名称需要从常量池中引用一个constant-utf8_info类型的常量来标识,至于属性值的结构完全可以自定要,只要说明这个属性值所占用的位数长度即可
-
下面就是规定了属性的名称索引,属性的长多,属性是由一个表info标识
微信截图_20181120235110.png
code属性表如下:
- code属性的名称固定为Code,抽象方法或者接口方法没有改属性。其他类的方法表会出现这个属性
- max_stack 操作数栈的最大深度,即有多少个局部变量
- max_Locals 代表局部变量表所需要的存储空间 这边单位是slot,除了double和long是需要2个slot 其他都是1个slot。slot可以重用,当某个局部变量在方法中超出其作用域,其可以被覆盖
- invokespecial的作用是以栈顶的引用类型的数据所指向的对象作为方法的接收者。只能调用三类方法:<init>方法;private方法;super.method()。因为这三类方法的调用对象在编译时就可以确定。
- invokevirtual是一种动态分派的调用指令:也就是引用的类型并不能决定方法属于哪个类型。
- code属性还包含异常表,异常表不是必须存在的包含四个字段,start_pc到第end_pc(不包含)出现了catch_type或者其他子类的异常,则转到第handler_pc行继续处理。当catch_type=0代表任何的异常都需要转向handler_pc行处理
- return 代表将值保存到returnVlaue中即最后一个本地变量表中的slot,当return真正执行的时候会把该值赋值到操作栈顶,作为方法返回值来使用。
exception属性
- 其余code属性平级,与我们之前说的异常表不一样。其作用是列举出方法中可能抛出的受检查异常,也就是方法描述在throws关键字后面列举的异常