JVM 类文件结构

整理自《深入理解Java虚拟机》

一、Class文件

类文件是由一个.java文件经过编译后形成的.class文件。class文件中包含了虚拟机指令集和符号表以及若干其他辅助信息。class文件交给JVM去运行,JVM再在不同的系统环境中执行代码。可以说这也是Java实现跨平台的重要一步。class文件不光只有Java可以生成,只要是运行在JVM上的语言经过不同的语言编译器编译后都可生成class文件在JVM上运行。

二、Class文件的组成

class文件由以下表构成(按顺序):

1、Magic Number(魔数):用于确定该文件是一个class文件。class文件中该值为四个16字节,值为0xCAFEBABE。

2、紧接着Magic Number的4个字节为版本号。高版本的JDK兼容低版本的JDK,但反过来不可以。

3、常量池(14种类型的表构成常量池)。

4、访问标志

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

6、字段表

7、方法表

8、属性表。属性表比较特殊,其跟在字段表、方法表后面。

三、详细介绍

1)常量池

常量池是Class文件中所占空间最大的数据项目。常量池中入口处为一个u2(两字节)类型的数字,表示常量池中有多少项常量。常量池中有14种表来表示常量,每个表都有相应编号(在开头)。比如CONSTANT_Class_info代表该类的信息。该表中的name_index若为2则代表指向第二个常量。第二项开头的索引为1,则代表为CONSTANT_Utf8_info表,后边的length长度的数字为该类全限定名的ASC码。(P169~P170)若常量池开头开头为0x16时说明有21项常量(p171),但常量项的索引却从1开始(1~21),这是因为当不引用常量池中任何一个项目时,将索引值置0。对于其他集合类型,如接口索引集合、字段表、方法表索引都是从0开始。

常量池主要存储下列两种内容:

(1)字面量

字符串、final static常量(constantValue会指向常量池中该值)等。

(2)符号引用

1、类和接口的全限定名。即带包名的完整类名,并把包中的 换为 / 分隔。

2、字段的名称(字段名)和字段描述符(字段所属的数据类型,比如int、char等)。

3、方法的名称(方法名字)和方法描述符(方法的参数列表和返回类型)。

14种表的详细结构见p169、p172。

2)访问标志

在常量池结束后,紧跟的两个字节为访问标志。该标志用于表示该class是类还是接口以及是否为public、abstract、synchronized等

该标志有16个标志位(ACC_XXX),将符合条件的标志位的值叠加生成访问标志的值。

表的详细结构见p173。

3)类索引(this_class)、父类索引(super_class)、接口索引集合

紧接着访问标志为的为这三个索引,class文件中的继承关系即由这三个索引确定

(1)类索引(两字节)

类索引用于确定这个类的全限定名。也就是说名字光存在常量池中了还不够,得有人指向它。this_class指向常量池中自己类的CONSTANT_Class_info表(常量池中第this_class项),这个表中的index在去指向储存着真正类名的CONSTANT_Utf8_info表。

(2)父类索引(两字节)

该索引指向这个类父类的全限定名。由于Java没有多继承,所以除了Object类,该索引只有一个。如何寻找类名与类索引一致

(3)接口索引集合(多个两个字节组成的集合)

虽然Java不允许多继承,但是可以多实现接口。所以接口索引第一个两个字节代表的是实现了多少个接口,也就是接口的个数。implements语句后的接口按顺序从左到右排列在接口索引集合中。

表的详细结构见p175。

4)字段表(大部分类不止一个字段,多个字段表组成字段表集合)

字段表用于描述接口或类中声明的字段,一个字段表对应一个字段。字段包括类级变量(static)以及实例级变量(new出来的)。字段表有以下几项:

access_flags(存放该字段的修饰符,如public、static、final、volatile等)、name_index(字段的名字的索引,指向常量池第name_index项)、descriptor_index(该字段的数据类型或对象类型的描述符,指向常量池第descriptor_index项)、attributes_count(这个字段表跟随attributes_count个属性)、attributes(属性表集合)。

举个例子,比如

public long x ;

该字段表的第一项为0x0001,代表public;第二项为字段名,即x;第三项为描述符,这里为J,代表数据类型为long。

final static int x = 3,

如果该字段为final static修饰的常量,那么字段表屁股会跟随一个名称为ConstantValue的属性表,其指向常量池中该字段的值(常量池中CONSTANT_Integer_Info类型表,值为3)。

表的详细结构见p176~p177。

5)方法表(大部分类不止一个字段,多个字段表组成字段表集合)

如果理解了属性表,那么方法表也只是如法炮制。方法表描述类中的方法(包括static方法),一个方法表表示一个方法,多个方法表组成一个方法表集合。方法表有以下几项:

access_flags(存放该方法的修饰符,如public、static、final、synchronized等)、name_index(方法的名字的索引,指向常量池第name_index项)、descriptor_index(该方法的描述符,指向常量池第descriptor_index项)、attributes_count(这个字段表跟随attributes_count个属性)、attributes(属性表集合)。其中方法里面的代码会存在名为Code的属性表中

举个例子,比如:

public int show(int x, char[] args) {
    System.out.print(args[0] + x);
    return ++x;
}

方法描述符的顺序为先参数列表,后返回值。参数列表按照参数的严格顺序放在一组小括号中,紧接着的为方法返回值描述符。

该方法表的第一项为0x0001,代表public;第二项为方法名,即show;第三项为方法描述符,这里为(I[C)I。

还需注意两点:(1) 如果父类方法在子类中没有被重写,方法表中就不会出现来自父类方法的信息 。这一点在继承中尤为关键,千万不要以为子类中有父类方法的拷贝,子类是通过父类索引找到父类的class文件从而调用父类(继承得来)的方法。

class Father {
	public int Conf;
	
	public void init(int Conf) {
		this.Conf = Conf;
		this.init();
	}
	
	public void init() {
		System.out.println("Father running.......");
	}
	
}

public class Son extends Father{

	public void init() {
		System.out.println("Son running......");
	}
	
	public static void main(String[] args) {
		Son f = new Son();
		f.init(6);

	}

}

 

上图是子类通过Java ByteCode Editor解析出的方法表集合。可以看出子类只有一个自己的init方法。

(2) 编译器也会添加自己的某些方法,如<init>、<clint>,这两个方法留在类文件的加载来讲。

表的详细结构见p178~p179。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值