目录
接着上一篇,在第50字节处数据有一个类型为u1=1的,即UTF-8编码的字符串变量a,在它之后又有一个UTF-8编码字符串“Ljava/lang/String”,表示a变量的类型是Scanner对象,元素字节数据第57字节一直到第75字节处。接着继续看下面的字节码文件部分。
标识
access_flags访问权限
上面一大堆常量池数组元素,之后接着的是第四部分,类的访问权限(上篇日志已经简单总结了.class字节码文件的前三部分:魔数、版本号和常量池),access_flags访问权限元素的标志位tag类型是u2,前面说过,u1类型占一个字节,u2类型占两个字节,还有u4和u8,分别标识4和8字节的无符号数。类的访问权限,很好理解,就是当前类,或这个接口的访问类型是public,private还是其他,access_flags的标志有如下类型:
0x0001 | ACC_PUBLIC | 是否为public访问类型 |
0x0010 | ACC_FINAL | 是否为final访问类型 |
0x0020 | ACC_SUPER | 是否允许invokespecial字节码指令 |
0x0200 | ACC_INTERFACE | 是否一个接口 |
0x0400 | ACC_ABSTRACT | 是否为抽象类 |
还有标识类的枚举,注解就不一一写上去了。
从上图两个字节access_flags数据可以看到,0x0021,表示的是该类的访问权限包括public和允许使用invokespecial字节码指令。
this_class包名和类名
字节码文件的下一部分是this_class,该元素保存的是类的全局限定名索引,简单来说就是保存了类的包名和类名,从下图可以看到this_class的值为0x01,表示对应一号常量池元素:
如果我们在命令行里用javap –verbose命令来查看Java字节码文件便可以看到,常量池Constant pool中的一号元素#1就是Class,类名为Hello。
super_class
下一个是super_class,也就是该Java类的父类索引名,很容易看懂,十六进制文件里super_class的值为0x03,表示对应的是常量池中的三号元素:
可以看到,常量池中的三号元素java/lang/Object,是类Hello的父类,由于Hello类没有显式去继承其他的类,所以它会默认继承java/lang/Object作为其父类。
interfaces_count
父类信息接着就是interfaces_count,类型是u2,占两个字节,记录了当前类的实现的接口数量:
因为我们这个例子很简单,Hello类没有实现任何的接口,所以interfaces_count的值为0,同样的,interface结构中还有个接口列表,interfaces_count,它是一个集合,里面的数据类型都是u2,存放了当前类实现的接口的索引,但因为Hello类没有实现任何接口,所以该集合为空。
字段
一些标识信息看完后,字节码文件往下走,可以看到当前类中的变量信息,首先是fields_count,标识的是类中定义的成员变量和类变量的总数:
fields_count的值为0x01,表示类中只包含一个变量,的确,从上面javap –verbose命令分析出的字节码文件可以看到,常量池中的5号元素#5就是我们类中的变量,只有一个变量a。
field_info fields
变量总数之后跟着的是field_info fields[fields_count],该集合显而易见保存的是具体的变量,集合中的元素,不同的变量数据长度是不同的,具体的fields结构有name_index,变量名称引用;access_flags,访问标识,有public、private、protected、final等,不同访问标识对应的十六进制值不同。descriptor_index,表示变量类型的引用。下面来看看我们的例子.class字节码文件中第一个变量a:
可以看到,前面两个字节标志位0x0001,表示该变量的访问标识值为1,对应的是public。下一个0x0005,表示改变量在常量池中的名称索引是#5,从上面的javap –verbose命令分析出的字节码文件可以看到名称是“a”,0x0006表示的是该变量的类型索引在常量池中是#6号元素,对应的是Ljava/util/Scanner,该变量是一个Scanner对象。
方法
fields[]字段列表看完之后,紧接着下面的数据反映的是methods_count和method[],前者表示当前类中的方法总数,后者表示类中所有方法的列表,这和前面变量Fields结构相似,首先来看methods_count:
同样是占两个字节的标识,0x0004表示该类中有4个方法,在Java程序中,一个类里包含的方法,除了我们自己定义的,还有些类初始化时需要用到的方法,例如<clinit>()方法,即class init,该方法的作用是,当Java类加载过程中,对其做一些初始化操作,相当于一个类的构造器。除了clinit方法外,还有一个init方法,它是对象构造器方法,顾名思义就是当程序运行时,执行到对象实例化new一个对象,就会去执行该init方法,对对象进行一些初始化操作。
method_info methods
跟在methods_count后面的就是methods[]集合,它里面存放了类中的每一个方法,methods结构和上面的fields结构十分相似,组成部分同样有access_flags访问标识、name_index,变量名称引用和descriptor_index变量类型的引用。对于方法的访问标识比对类中变量的访问标识多很多,相同的有public、private、protected、static和final,方法的访问标识多出的有native、abstract和strictfp关键字等,对应的标志位都可以很方便地从access_flags可选项值里查阅。