JVM(十一)基于无符号数和表结构的类文件结构

Java的跨平台性

就是因为java编译器(javac)生成了.class文件,可以在任何只要安装了虚拟机就可以执行的字节码文件,java虚拟机没有和任何编程语言绑定,包括java语言,他只和.class二进制文件有关


 

字节码文件组成(.class)

以8位字节为单位的二进制流文件,当遇上需要占用8位字节以上的数据,就分割成若干个8位字节进行存储

整个class文件可以看成是一张表,采用无符号数和和组成

无符号数

是一种基本数据类型,u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节长度的无符号数。

是有多个无符号数和其他表组合构成的复合型数据结构,所有表都已_info结尾。所以说整个class文件就是一张表 

当需要描述同一类型但数量不定的多个数据时,会使用一个前置的容量计数器加若干个连续的数据项形式来表示,称为某一类型的集合,如后面会提到的字段表,方法表等都是这样的格式。

 


 字节码文件具体内容

(1)魔数

一个字节码文件都是以魔数开头的,魔数是很多文件的存储标准用来区别文件类型的,比如class文件的前四个字节用来存储魔数,魔数是0xCAFEBABY,不因后缀名或者叫拓展名来区别文件类型,是因为拓展名都是可以更改的。

(2) 版本号

接着魔数的就是class文件的版本号,5,6两个字节是次版本号,7,8两个字节是主版本号

(3)常量池(constant_pool_count)

长量表紧挨着版本号,可以说是class文件的资源仓库,后面的方法表,字段表都要有指向常量表中常量的引用。在常量池里会把代码中所有的常量(一般包括字面量,和符号引用,不是平时说的被final修饰的“常量”)都存储起来,并设置索引。常量池是class文件中第一个表结构。

  • 字面量:比如字符串常量"hello word",以及final修饰的,比如final int MAX=123;123就是字面量
  • 符号引用:类和接口的全限定名;字段的名称和描述符,比如public int a=0;这里a是字段名称,int是描述符;还有方法的名称以及描述符,比如public int add(int a,int b){},那么add是名称,int是描述符。前面我们说过在符号引用阶段,不涉及jvm的内存布局信息,只有在解析阶段以后,符号引用转化为直接引用,才会把引用翻译到具体的内存地址上。

常量表的大小是不固定的,取决于你程序中有多少个常量,所以在常量表的开始,放置一个u2类型的数据,就是前面说的容器计数器,来表示常量表中有多少个常量,比如一个有20个常量,那么他们的索引取值范围是1-20,后面用到某一个常量时,就可以用"#索引值"来访问常量。至于索引取值为0,表示数据没有引用任何常量(特定情况下使用)。

在常量池中存储的每一个常量都是以表结构存储在常量池中

 有14种表结构,每一个常量对应这14种表结构中的一种。

这里主要说明一下CONSTANT_Utf8_info,以及CONSTANT_Class_info这两种类型的表结构,我们知道常量池中第一个常量肯定是类或接口的符号引用,然后就会是他们的全限定名常量,比如我的public class Test,全限定名是xidian/lili/mytest/Test.

那么在常量池中第一个常量类型就是CONSTANT_Class_info,它的表结构有两个参数,第一个是u1类型的tag,表示当前常量的类型,那么我们这里第一个常量的tag=0x07(图中圈出来);第二个参数是u2类型的name_index,指向一个类型为CONSTANT_Utf8_info的常量。我们知道类或接口的符号引用一定会有描述信息(字段和方法也一样),这里类的描述信息就是字符串"xidian/lili/mytest/Test",这个字符串是常量池的第二个常量,类型是CONSTANT_Utf8_info。所以我们第一个常量的name_index=#2(他的描述在第二常量)。

那么如上所说第二个常量是CONSTANT_Utf8_info,在class文件中,类,接口,字段,方法的描述信息都是CONSTANT_Utf8_info类型的常量来描述的。它有三个参数,第一个是u1类型的tag,表示当前常量的类型,那么这里tag=0x01;第二个参数是u2类型的length,表示描述信息的长度,比如length=0x001D,说明描述信息长度是29,第三个参数是u1类型的byte,它的数量等于length,那么我们就往后数29个字节,都表示描述信息,每一个字节是一个byte,可以根据ASCII换算回我们的字符串。

以上就是两种比较简单的常量类型。

(4)访问标志

 常量池之后就是访问标志(access_flags)信息了。是一个u2类型的数据,用来描述类的访问权限,比如0x0001(ACC_PUBLIC)代表public等等,一共有16种标志

(5)类索引、父类索引、接口索引

这三个按照顺序排列在访问标志之后,前面我们说了常量池,那么这些索引(包括这三个索引以及后面要说的字段表和方法表中的索引)就是执行常量池,然后在运行期间,根据这些索引找到常量池存的内容进行操作。 

那么这三个索引就确定了类的继承关系。

类索引和父类索引都是u2类型的数据,而接口索引是u2类型数据的结合(一个类可以实现多个接口)。所以接口索引集合入口第一项是一个u2类型的接口计数器。

对于前面的Test例子,我们没有继承的类和实现的接口,但是父类有java.lang.Object.所以这三个u2数据类型会依次是:

0x0001、0x0003、0x0000

0001表示我们的类索引是常量池中的第一个变量,如上面所说

0003表示我们类的父类Object是常量池中的第三个索引,如上面所说第二个常量是类的描述信息,所以父类会是第三个常量

0000表示这个类没有实现接口,如果这个数字不为0,那么后面继续加接口对应的常量池的索引,这个数字是多少,就会加多少个接口索引。

(5)字段表集合

这里说的字段包括java程序中的类属性和实例属性,但是不包括方法中的局部变量

一般一个类中是有多个属性的,所以字段表是一个多个字段信息的集合,而每一个字段由是一个表结构,同理,字段表集合入口处会有字段计数器。对于一个字段来说,包含的信息有很多,作用域,是否是静态变量,可变性(final),并发可见性(volatile),字段数据类型,字段名称,可否被序列化等都需要借助常量池中的索引来描述。

对于一个字段表结构,它有5个参数。

1.  u2类型的access_flags:与类的access_flags相似,比如access_flags=0x0002,表示这个字段是private修饰的

2.  u2类型的name_index:对常量池中字段的名称的索引

3. u2类型的descriptor_index:对常量池中字段描述符常量的索引,对于字段和方法的描述符合与上面说的类的描述信息不一样(类的全限定名)

比如java程序中有:private int m;

那么access_flags=0x0002,name_index=0x0005,descriptor_index=0x0006

0005表示这个字段的名称是常量池中的第五个常量,类型肯定是CONSTANT_Utf8_info,内容(byte)是m

0006表示这个字段的描述符是常量池中的第六个常量,类型肯定是CONSTANT_Utf8_info,内容(byte)是I,字段描述符用一个大写字母来表示,I代表int,V代表void,D代表double,L代表对象类型等等,这些字段描述符也是方法的描述符,用描述符描述方法时,按照先参数后返回值来描述。参数放在()类,比如void com(){}方法,描述符就是"()V",无参数无返回值。

4. u2类型的attributes_counts:属性表计数器

5. attribute_info类型的attributes:属性表,这两参数用来存放字段的额外信息,那比如private int m,属性表计数器就是0,没有属性表这个参数,比如final static int  m=20;就会有一个ConstantValue属性。具体的后面谈到属性表再讲,因为方法中也有属性表。

字段表中不会表示从父类继承来的属性,在内部类中会自动添加指向外部类实例的字段,在字节码文件中,两个字段的描述符不同就不是同一个字段,但是在java中,修饰符,数据类型不管是不是相同,都必须使用不同的属性名称。

(6)方法表集合

了解了属性表结合,方法表集合类似。结合入口是方法表计数器。每一个方法是一个表结构,类似字段表结构。

特别的方法表的访问标志没有acc_volatile,acc_transient,但是增加了acc_synchroniaed, acc_abstract,acc_native,acc_strictfp等只能修饰方法的修饰符。还有就是在java代码中,实现方法体的代码,都放在方法表的code属性中,是属性表的一种类型,下面会介绍属性表。

我们知道java方法重载中,除了方法名必须一样,还必须有一个与原方法不同的特征签名,特征签名就是方法中各个参数在常量池的字段符号引用的集合,也就是因为java的特征签名不包括返回值,所以无法仅仅依靠返回值的不同来进行重载。

(java代码层面的特征签名包括方法名称,参数顺序,参数类型。而字节码的特征签名还包括返回值以及受查异常表)


以上就是一个字节码文件包括的几部分内容,下面将介绍在在字段表和方法表中都出现的属性表,当然在class文件中也可以有自己的属性表


属性表集合

预定义的属性已经有21种,对于每个属性,他有三个参数。

u2类型的attribute_name_index,是从常量池中引用一个CONSTANT_Utf8_info类型的常亮来表示

u4类型的attribute_length  u1类型的info

code属性

就是java程序代码经过javac编译之后,变成字节码指令存储在code属性集合中,code属性出现在方法表后面的属性集合中。

max_stack代表了操作数栈深度的最大值,方法运行期间操作数栈都不会超过这个深度,虚拟机就是根据这个值来分配方法区栈帧的操作栈深度。

max_locals代表了存储了局部变量表需要的内存,max_locals的单位是变量槽slot,slot是虚拟机为局部变量分配内存使用的最小单位。对于不足32位的数据类型,占用一个slot。64位的long和double需要占用两个slot。不是每个变量所占的slot之和就是max_locals的和,因为局部变量表中的slot可以重用。当一个变量过了自己的作用域,虚拟机会把它的slot分配给其他变量使用

code_length和code用来存储代码翻译过来的字节码指令,code中存储的就是字节码指令,根据一个字节一个字节对照字节码指令表,翻译出字节码指令。

可以在code属性表的args_size看出任何方法都有一个this对象的属性,比如构造方法和无参数的方法,args_size=1.可见这个实现是编译器把this对象当做参数传入,局部变量表也会把第一个slot空出来存放这个实例对象的引用。

在字节码指令之后,存放的是这个方法的显示异常处理表,这个表不是必须的

异常表有四个参数

u2类型的start_pc(from),end_pc(to),handler_pc(target),catch_type(type).代表了在运行start_pc到end_pc之间的代码,出现了类型为catch_type或者其子类的异常,则转到handler_pc行继续执行

try-catch-finaly语句执行语句

(1)try语句中出现属于catch的异常或者其子类,转到catch块中处理

(2)try语句中出现出现不属于catch捕获的异常,转到finally语句块处理

(3)catch语句块捕获到异常,则转到finally语句执行

出现异常或者正常执行都会执行finally语句块

code属性是class文件最重要的一个属性,java执行字节码是基于栈的体系结构

Exception属性

区别与上面的异常表

Exception属性的作用是列举出方法中可能抛出的受查异常,也就是方法描述时throws关键字后面列举的异常

LineNumberTable属性

描述Java程序代码行号与字节码指令行号之间的对应关系,默认会出现在class文件,可以用-g:none或者-g:var来选择打开或者关闭

ConstantValue属性

作用是通知虚拟机自动为静态变量赋值。只有被static修饰的变量(或者类变量)才能拥有这个属性

int a=10;普通变量没有constantvalue属性,对于这种变量的赋值是在实例构造器<init>方法中进行的。对于类变量

static int a=10;可以有两种方式进行赋值

可以在<clinit>方法中或者使用constantvalue属性赋值。习惯上在sun javac的编译器,如果变量是static final修饰的基本数据类型常量或者字符串常量(常说的常量)就用字段表后面的constantvalue属性赋值。如果这个变量没有被final修饰或者不是基本数据类型或者string类型就用<clinit>来赋值

InnerClasses属性

记录内部类和宿主类之间的关系。如果是匿名内部类,参数inner_name_index是0

Signature属性

专门用来记录泛型签名信息。专门设置这样一个属性,是因为java中采用的泛型擦除,在字节码中,泛型信息(类型变量,参数化类型)都别擦除。所以反射调用时无法获得泛型信息。signature属性就是为了弥补这个缺陷,现在的java的反射API获取的泛型类型数据都是来源与这个属性

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值