Java类文件格式快速入门

原文地址:http://viralpatel.net/blogs/2009/01/tutorial-java-class-file-format-revealed.html

原作者:Viral Patel

译者:Alan Gao @ cgaolei.iteye.com

 

译者序:

最近工作解决一个问题时需要用到Java bytecode的知识,临阵磨枪学习了一下,还真的对java bytecode产生的很大的兴趣,打算平时再深入研究一下。学字节码时,我是先从类文件的格式入手的。当然,学习这方面东西,最权威不过的还得是<<Java虚拟机规范了>>:http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html。 只不过那里写的过于正规详细,需要一定时间理解和消化。随即搜索了一下相关的文章,于是找到了Viral Patel的这篇教程。文章图文并茂,简单易懂,短短几句话就含盖了重要的知识点,可以让人快速勾画出类文件的轮廓。做为快速入门和大概了解类文件的教程最合适不过了。又搜索了一下中文的资料,相关的文章也不少,但感觉都没有这篇适合。所以翻译一下做为参考。

 

译文:

 

我们所看到的Java字节码是被封装在一个类文件中(扩展名为.class)。在这个教程中,就让我们来看看类文件的内部构造。

数据是如何被写入类文件以及类文件的格式是怎样的呢,让我们首先图解看一看Java类文件。

 

Java类文件结构示意图:

 

上图描绘的Java类文件被分为了不同的区段,包括魔术码(magic)、版本(version)、常量池(constant pool)、访问标识(access flags)、(this)类、(super)类、接口(interfaces)、域(fields)、方法(methods)和属性(attributes)。

 

首先Java类的长度在它被加载之前是未知的,因为类中含有可变长度的区段,如常量池、方法、属性等这些区段。但这些区段的开头字节都是该区段的大小或长度信息,这样JVM在实际装载它们之前会知道这些可变长度区段的大小。写入类文件的数据是以紧凑的单字节存放的,这有助于减小类文件的大小。Java类文件中不同区段的顺序是严格规定的,以便JVM可以按其顺序载入不同区段。让我们详细看看类文件中的每个组成部分。

 

(译者注:这十个区段M agic, V ersion, C onstant pool, A ccess flags, T his, S uper, I nterfaces, F ields, M ethods, A ttributes 连起来可以用一个句英文方便记忆它们的顺序:M y V ery C ute A nimal T urns S avage I n F ull M oon A reas . 我非常可爱的小动物在滿月的地方变得疯狂。)

魔术码(magic)区段

魔术码是用来识别文件格式以便区别于其他格式。类文件的前四个字节,也就是魔术码,0xCAFEBABE。(译者注:16进制编码的写法即是英文单词CAFE BABE[咖啡宝贝],以方便记忆)


 

版本号(version)区段

 

紧跟着的四个字节包含了类文件的主要和次要版本号 。版本号数字可使JVM核实并确定类文件是否兼容。如果版本号大于JVM可以加载的版本号,类文件将无法被载入。


常量池(constant pool)区段

类或接口所定义的常量都会被储存在常量池,包括类名,变量名,接口名称,方法名称及签名,常量值,字符串文字等。常量被作为一个元素长度可变的数组存储在常量池。常量数组大小是可知的,因此,JVM在载入类文件时就知道将有多少常量在常量池。



上图中,用绿色表示的部分就是数组的大小。每一个数组元素第一个字节被用来标记这个常量的类型。上图中橙色表示的部分就是标记字节。虚拟机JVM即通过读取这个字节来识别常量的类型。假如一个字节的标记表示这是一个字符串,那么JVM就知道下面2个字节代表的是字符串长度以及之后的部分即是字符串本身。

 

(译者注:原文对常量池的描述还缺少一个关键内容:常量池计数的值是常量池中元素总数量+1,元素的下标从1开始。例如图中计数为3,那么真正紧跟着计数只有两个元素,下标为1,2。原文的配图多一个元素,在这里改正)


访问标识(access flags)区段

访问标识区段就在常量池区段之后。访问标识区段只有2个字节的数据,用来标识该文件定义的是一个类还是一个接口,是public,abstract还是final的。

This类区段

This类区段也是一个只有2个字节的数据,它的值是常量池中常量数组的一个下标。


 
上图中,This类的值0x0007指向了常量池中的一个常量。那个常量池中相应的条目由两部分组成,第一部分是一个字节的标记,代表了常量池条目的类型,是一个类或是一个接口。由上图中,橙色部分标识的常量条目的类型。后面跟着的两个字节的数据,数据的值又是常量池中的另一个常量值。图中,两个字节的值为0×0004,它指向的是常量池中的接口或类名称的字符串常量。

Super类区段

This类区段之后的2个字节就是Super类区段。结构上和This类很接近,这两个字节的值也是指向常量池里一个下标,而这个常量值后面部分指向的另一个常量值即是指向父类名的字符串。

接口(interfaces)区段

这个类或接口实现的所有接口都定义在了类文件的接口区段。接口区段最开始的2个字节提供了实现的接口的数量信息。紧接着的是一个数组,这个数组中包含着指向常量池中所实现的接口名称的字符串量常下标。

域(fields)区段

域指的是这个类的实例或接口中所定义的变量。每个类文件的域中只包含了这个类或接口内定义的变量,而不包含从父类或父接口中继承的变量。域区段中的前2个字节是一个计数,记录的是这个域中所定义的变量总数。计数之后跟着的是一个数组,数组中每个元素即是一个域变量的定义,变量定义是由长度不同的结构体组成的。有些部分变量的定义是储存在结构体中,还有些部分,如变量名是保存在常量池中的。

方法(methods)区段

方法区段中存放的是这个类或接口中所定义的所有方法,和域一样,这里不包含从父类继承的方法。区段中的头2个字节记录的也同样是这个类或接口定义的方法的数量。其余的部分同样也是一个数组,数组中的每个元素即是一个方法的结构体。每个方法结构体中包含着这个方法中的一些信息,如参数列表,返回类型,方法局部变量所需要的堆栈字数,方法运算栈所需要的堆栈字数,Exception表及字节码序列等。

属性(attributes)区段

属性区段含有这个类文件的一些属性信息,如其中一个属性是源代码属性,用来表示这个类字节码文件的源代码文件名。属性区段的头两个字节是区段中属性的个数,跟着的是属性本身。JVM会忽略掉任何它不识别的属性。

你可能感兴趣的:(java,jvm,数据结构,虚拟机,Access)