JAVA虚拟机入门(1)---------类文件结构 (下)

大家好,这篇博客是继JAVA
虚拟机入门(1)—–类文件结构(上)
的,没看过那篇博客的最好先看那篇博客~

由于始终围绕上篇博客的例子进行讲解,这次还是先把图放出来,这图和上篇博客的一模一样,只是为了大家方便观看。

这里写图片描述

(五)类索引、父类索引,接口类索引集合(至少4个字节)

承接在访问标志后的就是类索引、父类索引,接口类索引集合。类索引用于确定这个类的全限定名,有2个字节;父类索引用于确定这个类的父类的全限定名,有2个字节,且除了Object类外,其余类这个索引不可能为空(都默认继承Object);接口类索引集合跟之前的常量池接口差不多,前两个字节表示一共有多少个接口,如果是00 00说明没有implement 接口。

还是针对上篇博客中的例子,跟在0021后面的就是 00 01,说明这个类的全限定名要去第一个常量找,找到常量池中的第一个常量,为07 00 02,上次我们解析过了,07代表的是CONSTANT_Class_info,是一个指向类的索引,这里0002指向的是第二个常量,由于之前已经解析过了,这里就不再赘述,第二个常量解析出来的结果正是javaLearning/test,也就是这个类的全限定名。

紧跟着00 01的是00 03,说明父类权限定名要去第三个常量找,步骤和上面的一模一样,解析出来的结果是java/lang/Object,没错吧!没有继承其他类的类默认继承Object。
接下来就是00 03后面的00 00,我们说过,00 00代表的是没有接口,在我们这个例子中确实没有继承接口,bingo。

(六)字段表

回顾一下,到目前为止,我们已经解析了魔数,版本号,常量池,访问标志和类索引、父类索引和接口索引集合,接下来将正式进入类里面探索每一个字段在字节码文件中怎么表示的。
在索引集合后跟着的就是字段表了。什么是字段呢?举个例子吧,private int k = 0,这里的k就可以理解为是字段。当然,字段实际上是包括它的属性的,比如private和int等。
首先我们先看一下字段表的结构,这会指引我们对字节码进行解析:

这里写图片描述

要注意表中的数据是针对于一个field(字段)的,但是我们知道,在一个类中,往往有许多字段,比如private int a; private int b; 这样的话就需要一个计数器来表示一共有多少个字段了,因此字段表一开始的2个字节就是用于表示一共有多少个字段的。在我们的例子中是00 01,也就是说只有一个字段,这是当然。因为我们就只有一个字段:private int a = 1;

接下来,我们将结合实例对里面每一个字段进行解释。

(1)access_flags(2个字节)

还记得(四)中的访问标志吗,这里的access_flags和那个差不多。下面是access_flags可以设置的一些属性。

这里写图片描述

不过注意里面的这些属性,有些是可以在一起设置的,有些是相斥的,具体哪些相斥可以参考java制定规则,这里由于将焦点放在解析字节码文件,因此不多赘述。
在我们的例子中,access_flags是在00 01后的00 02,根据上面的属性表可以得知对应的是ACC_PRIVATE,正确。

(2)name_index(2个字节)

name_index表示的是字段的简单名称的索引,何谓简单名称呢?这是相对于全限定名而言的,在(五)中我们已经知道,全限定名就是类似于javaLearning/test这种结构的(包/类),而简单名称则就是一个名字而已,比如private int a = 1(还是这个例子),简单名称就是a。
知道这个概念后,看回例子,name_index为00 05,去常量池的第5个常量找,对应的位置是Offset为00000030那一行的01 0001 61,这个就是第五个常量了,01表示是CONSTANT_Utf8_Info,0001说明长度为1个字节,61表示的是‘a’,怎样?是不是我们例子中的那个字段a!再一次成功解出。

(3)descriptor_index(2个字节)

我们已经解析出来private和a,但是还有一个关键的a的类型还没解析出来,那应该怎么得到a的类型int呢?这就需要描述符了。
描述符是一个相对于前面两个复杂一点的东西。它主要是用于描述字段的数据类型、方法的参数列表(包括数量、类型和顺序)和返回值。首先我们先讲解描述符的描述规则,一旦理解这个,解析也就so easy了。
根据描述符的描述规则,基本数据类型(int,byte,char,double,float,long,short,boolean)以及代表无返回值的void都用一个大写字符表示,而对象类型就用L + 对象全限定名表示,而数组类型每一个维度都用一个前置的”[“表示。
举个例子:String[][][]表示为[[[Ljava.lang.String,int[]表示为[I
具体如下表:

这里写图片描述

大家也注意到了,在说描述符时,我并没有局限于描述字段,而是提到了方法,没错,描述符也用于描述方法。用描述符描述方法时,按照先参数列表再返回值的顺序描述。参数列表按照参数的严格顺序放置在()中。继续举个例子:
int getIntTest()用描述符描述就是()I,void k(int a,String[]k,int b)用描述符描述就是(I[Ljava.lang.StringI)V。
相信大家对描述符怎么得来的应该已经有了一定理解,接下来就是实战了。
再次献上那个远古的例子(参照本博客刚开头那张表),在实例中descriptor_index对应的字节码是00 06,去常量池中第六个常量中找,位置是offset为00000030的01 00 01 49,对应的值为I,也就回答了我们在前面提到的问题了,这个I就是int。
在descriptor_index后跟着的是attrubute_count,但是因为在这里是00 00,说明没有其他属性了。不过如果是final static int a = 9;那么属性表集合将会有一个指向常量池中9的属性。

(七)方法表集合

在字段表后面就是方法表集合了,由于方法表集合和字段表集合十分类似,因此直接给出方法表结构以及方法表访问结构
方法表结构如下:

这里写图片描述

方法访问标志如下:

这里写图片描述

接着就直接对实例中的方法表进行解析了(毕竟真的和字段表很像):

看到Offset为000000E0中的00 02,说明方法表一共有两个方法。咦?我们不是只写了一个方法吗?怎么这里说是两个?其实,要注意到,每个类都会有属于自己的构造函数,如果自己没有写构造函数的话,那么编译器会帮我们添加一个实例构造器,因此这多出来的一个方法就是构造方法了。

接下来看第一个方法,00 01说明是ACC_PUBLIC,00 07找到常量池的第七个常量,位于Offset为00000030的01 00 06 3C 69 6E 69 74 3E,翻译出来就是,再看00 08指向常量池的第8个常量,读出来为()V,说明是返回值为0,参数为空的函数;接下来的00 01说明属性表只有一个属性,是00 09,读取第9个常量,读出来是Code,说明这个属性是方法的字节码描述。总结一下就是我们读出来一个public的无参构造函数,而关于方法的字节码描述在后面的属性表中会进行解释。

第二个方法是00 01 —-> public,00 09,找到第九个常量,0A 00 03 00 0B,关于常量池中的tag在上篇博客有给出一张表,根据里面的规则进行转化,最后的结果是getIntTest,就是方法的名字了。

注意:

(1)一个类如果没有override一个父类的方法,那么父类的方法不会出 现在这个类的常量池中

(2)在java中重载一个方法,其实除了一个和原方法一模一样的名称外,还需要一个与原方法不同的特征签名。什么是特征签名呢?特征签名是一个方法中各个参数在常量池中的字段引用的集合,而返回值是不属于方法中参数的,因此无法通过返回值来对一个原有的方法进行重载。

(3)在Class文件格式中,特征签名的范围更大一些,只要不是完全一样的描述符就可以了,因此只有返回值不同也是可以算作重载的。因此同一个类中如果两个类只有返回值不同时可以的,因为这个类被编译成.class字节码文件后的描述符是不一样的

七:总结

原本在方法表后就是属性表了,但是还记得我在类文件结构(上)一开篇提到的吗?我是为了学习热修补而开始学习java虚拟机的,因此关于属性表和后面的知识,解析起来方法上是一样的,为了让我的目标更明确,我决定类文件结构就到此为止。

当然,如果有比较多的朋友还想我写属性表和后面的内容,我会找个时间写一遍的。

下一篇博客就是类加载机制了,这一块是直接和热修补技术相关的,因此希望大家继续耐心地支持我,谢谢。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值