首页 > 代码库 > JVM中class文件探索与解析(一)

JVM中class文件探索与解析(一)

一直想成为一名优秀的架构师的我,转眼已经工作快两年了,对于java内核了解甚少,闲来时间,看看JVM,吧自己的一些研究写下来供大家参考,有不对的地方请指正。

废话不多说,一起来看看JVM中类文件是如何加载和运行的。

(1)首先,编写简单代码,对其编译生成的class文件进行研究,其java代码如下:

技术分享
 1 public class test {
 2     private static int count = 0;
 3     public static void recursion(){
 4         count++;
 5         recursion();
 6     }
 7 
 8     public  static  void  main (String args[]){
 9         try {
10             recursion();
11         }catch (Exception ex){
12             System.out.print("deep of callings:"+count+"\n");
13             ex.printStackTrace();
14         }
15     }
16 }
View Code

编译之后,用WinHex软件打开其class文件,可以看到其编译的十六进制文件如下:

技术分享

  按照上图分析,开头的前4个字节技术分享,是魔数(类似于拼音“咖啡宝贝”),它的用处是标识该文件是否能被java虚拟机识别;

  紧接着魔数的4个字节技术分享,前两字节0x00代表次版本号(小数点之后的数字),后两字节0x0033代表是class文件的主版本号,换算成十进制是51,标识是JDK1.7可识别的版本(不同的版本可以查看class文件版本号表如下:)

版本号对应十进制jdk版本号
2E 46 jdk1.2
2F 47 jdk1.3
30 48 jdk1.4
31 49 jdk1.5
32 50 jdk1.6
33 51 jdk1.7
34 52 jdk1.8

  在主版本号字节之后的是常量池,可以理解为Class文件的资源仓库,存储着与class文件相关的数据项。由于不同class文件,常量池数量不同,常量池入口放置两个字节的数据技术分享(0x0028)为常量池计数器。十六进制的0x0028为十进制的40(地址偏移量),代表常量池中有39个常量,索引范围为1-40(注:java仅限于class文件结构中容量计数器是从1开始的,java的设计者将索引0拿出来是有特殊考虑的,用来表示不引用任何一个常量池中的项)。

  常量池中,存放两类数据:(1)字面量:可以理解为java中的常量,例如:字符串、final修饰常量等。

              (2)符号引用:主要包括①类、接口的全限定名②字段的名称和描述符③方法的名称和描述符

  在常量池里,存储常量结构如下:u1(常量标志位,用于指明常量的类型,可以查看如下常量池项目类型对应表)+常量信息

技术分享

  让我们以上述class文件为例,索引为1的常量标志位是0x0A(十进制为10),对应上表中的CONSTANT_Methoddef_info类型的常量,参考常量结构表如下图(在jdk1.7中新增了tag=15/16/18的常量类型,更好的支持动态语言的调用,此处就不列举了),

技术分享

  该class文件中,常量池里索引为1的常量(const#1),项目类型标识符为0x0A,二进制为10,查询上表,代表着类方法的符号引用。紧接着两个u2字符代表该常量的信息内容,其中方法描述符0x0006为#6常量,名称及类型描述为0x001A指向#26常量。

  紧跟其后的是索引为2的常量(const#2),其标志符为0x09(十进制为9),是字段的符号引用,紧接着的两个u2字符代表其引用索引ID,方法的类描述符指向#27常量,字段描述符指向#28常量;

  分析了以上两个字节之后,这里就不一一分析后面的常量了,有兴趣的可以自己分析下。其他的常量池用jdk自带的javap进行生成,在windows中打开cmd(安装jdk并配置环境变量),输入:javap -verbose class文件路径,可以看到编译之后的常量池如下:

技术分享

   将上述class文件中常量池部分标记图如下,红色框代表一个常量池中的项,依次编号为1-39,

技术分享

  我们将上图和javap生成的常量内容对比一下,以const#9为例,#9项为:0x01 (utf8类型) 0x0006(占用字节) 0x3C 0x69 0x6E 0x69 0x74 0x3E(项内容),我们对项内容进行在线转换,将十六进制转换ASCII码值,得到该常量表示:<init>,如下图:

技术分享

  与javap生成的常量文件对比,发现两者完全一致。对字节码有兴趣的朋友可以逐个试一试。

技术分享

  在常量池区域结束之后,紧接着的一个u2(两个字节)类型的字符代表访问标志,它用于识别类或者接口的访问信息,例如:class是类还是接口,访问是private还是public等。访问标志表如下图:

 技术分享

  在上述文件中,访问标志为:技术分享,即:0x0021,对照上表,只有ACC_PUBLIC和ACC_SUPER为真,其他几项为假。该类为public 能够使用invoke指令。

  跟在访问标志之后的分别是类索引、父类索引技术分享。由于java不允许多继承,所以类索引和父类索引是一个u2类型的数据。在上述文件中,类索引为#5常量(TestClass),父类索引为#6常量(java/lang/Object);

  紧接着类索引和父类索引的是接口索引信息。在java中一个类可以实现多个接口,所以用u2类型的数据集合来表示接口索引。在接口索引的入口,有一项u2类型的接口计数器技术分享,计数器为0表示接口的索引表不占用任何字节。

  在接口相关描述信息之后的,是字段表集合,用于描述类或者接口中申明的变量(注:此处的变量是指类或者接口级变量,即类变量或者实例级变量,而不包括方法中的局部变量)。这些字段通常包含哪些信息呢?通常有:字段访问域(private、public、protected等)+是实例变量还是类变量(Static)+是否可修改(final)+并发可见性(volatitle,用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最终的值)+可否序列化(transient)+字段类型(基本类型、对象、数组)+字段名称。在JVM中,字段表结构如下:

类型     名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

  我们来看,字段表集合的入口u2类型数据项是字段表的容量计数器为技术分享(0x0001),表示只有一个字段项。紧接着字段表容量计数器的u2类型的数据为0x0002,参考下表,表示该字段为private。字段名称技术分享为0x0007,其值为“m”,描述信息0x0008,其值为“I”,可以推断,原代码的定义字段为:“private int m”;

标志名称 标志值 含义
ACC_PUBLIC 0x00 01 字段是否为public
ACC_PRIVATE 0x00 02 字段是否为private
ACC_PROTECTED 0x00 04 字段是否为protected
ACC_STATIC 0x00 08 字段是否为static
ACC_FINAL 0x00 10 字段是否为final
ACC_VOLATILE 0x00 40 字段是否为volatile
ACC_TRANSTENT 0x00 80 字段是否为transient
ACC_SYNCHETIC 0x10 00 字段是否为由编译器自动产生
ACC_ENUM 0x40 00 字段是否为enum

  通常而言,在字段描述之后还有一些属性表信息存储额外的信息,在以上class文件中,属性计数器位0x0000,表示没有额外的属性信息。

  在字段表之后的是方法表集合,其表示方法与字段信息表几乎一致。其结构如下表:

类型     名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

  但是,方法表的修饰属性比字段表要多,例如:方法有abstract、synchronize等。在方法表的入口,同样也有一个方法容量计数器,占用u2字节。在上述class中,方法的计数器为技术分享,即0x0003表示有三个方法。

  第一个方法,function#1的第一、二、三、四、五项u2数据项分别为:0x0001、0x0009、0x000A、0x0001、0x000B,代表方法为public、方法名指向const#9("<init>")、方法描述为const#10(“()V”)、含有一个属性、该属性指向const#11(“Code”)属性,java呈现方法体重的代码经过javac编译之后,就存储在Code属性里。

  (ps:今天的class文件探索就写到这里,过几天接着写...休息一下)

  

 

  

  

JVM中class文件探索与解析(一)