真正理解class文件的常量池 class文件的加载过程

class文件常量池可以说是class的资源库,可以说它包含所有class文件需要的字符

再看class文件结构

再次引入class文件结构图如下图:

class文件的加载过程(真正理解class文件的常量池)(1)

在上一篇文章中分析了class文件的前三个结构magic、minor_version、major_version,这三个结构除了主版本跟随jdk版本变化外其他都是固定不变的,是class文件的的基础。

紧接着的结构是constant_ pool_count、constant_ pool常量池数量与常量池,常量池数量u2类型的结构,暂用两个字节,里面的内容表示接下来会有多少个常量。

constant_ pool可以把他想成一个数组,他的长度是constant_ pool_count-1,因为constant_ pool是从1开始计数的,第0项作为预留可以表示什么都没有的意思,比如后面有索引要指向常量池的数据,但是实际值是不存在,则可以指向0(写在这里可能难以理解,不过等看完这篇文章在回头来看应该就理解了)。

常量池数据结构

前面的结构通过固定暂用几个字节就表达需要表达的含义,而接下来的常量池则要表达的内容要多得多,所以不是仅仅通过固定的几个字节就能够编译出二进制表达的含义,而是通过定义一些常量类型来表示,每个常量类型它有自己的独特结构以此来表达它的含义,常量池类型发展至今已经有17个,下图是常见的11个。

常见的11个常量池类型如下图:

class文件的加载过程(真正理解class文件的常量池)(2)

常量列表示他的名字不在class文件中表示,项目表示每个常量类型的结构,tag表明常量类型,byte表示常量项包含的具体内容。第一项CONSTANT_Utf8_info表达的是一个字符串,包含一个length结构表示这个字符串有多少个字节组成。还有一些项目包含index结构,表示他是通过引用常量池的其他常量项来表达(下一节分析具体示例能更加理解)。类型表示各个常量项各个结构暂用的字节数。

常量池示例分析

还是按照上一篇文章的class文件继续分析,文件内容图如下图:

class文件的加载过程(真正理解class文件的常量池)(3)

上一篇文章我们分析了前8个字节"CA FE BA BE 00 00 00 34",通过前面两节的分析我们知道接下来两个字节是constant_ pool_count,"00 18"转换成10进制是24,所以说明接下来有24-1=23个常量项

接下来的内容就该是常量项:

我们知道常量项最前面的结构都是tag都占一个字节,所以"0A"表示第一个字节的tag,十进制表示是10,通过查表知道tag等于10的常量类型是CONSTANT_Methodref_info(结构tag U1;index U2;index U2),说明这个常量表达的是一个方法的表达式,同时通过结构知道接下来的2个字节表达的方法的类描述符所在的常量项,"00 04"说明常量池第4项是这个方法的类描述符,接下来两个字节是方法的名称和类型,"00 14"十进制是20说明常量池第20个常量;

第2个常量池tag"09"表示这个常量项是CONSTANT_Fielderf_info(结构tag U1;index U2;index U2)类型,然后接下来4个字节"00 03 00 15"说明这个字段的类和描述符分别存在常量池的第3、21项;

第3个常量池tag"07"表示CONSTANT_Class_info(tag U1;index U2)是一个类的全限定名称,接下来两个字节"00 16"表示类的全限定名称在常量池第22项;

第4个tag"07"和上一个一样,"00 17"表示类的全限定名称在常量池第23项;

第5个tag"01"表示CONSTANT_Utf8_info(tag U1;length U2;byte U1)是用来表达字符串的,接下来2个字节"00 04"表示字节长度,说明接下来4个字节都是字符串的内容"76 61 72 31",通过ASCII表查询结果为var1,这是因为我在类中定义了一个变量var1;

第6个tag"01",又是一个字符串,长度为"00 01",内容"49",ASCII表查询结果I;

第7个tag"01",长度"00 06",内容"3C 69 6E 69 74 3E",结果<init>,在字节码里面构造方法就是<init>,这个后面说明;

第8个tag"01",长度"00 03",内容"28 29 56",结果()V,在字节码里用来表达方法无参数返回值为void,与<init>()V可以表达一成一个无参构造方法;

第9个tag"01",长度"00 04",内容"43 6F 64 65",结果Code;

第10个tag"01",长度"00 0F",内容"4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65",结果LineNumberTable;

第11个tag"01",长度"00 12",内容"4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 ",结果LocalVariabletable;

第12个tag"01",长度"00 04",内容"74 68 69 73",结果this;

第13个tag"01",长度"00 1E",内容"4C 63 6F 6D 2F 64 67 67 63 63 2F 74 65 73 74 2F 6C 65 69 2F 43 6C 61 73 73 54 65 73 74 3B",结果"Lcom/dggcc/test/lei/ClassTest;";

第14个tag"01",长度"00 07",内容"67 65 74 56 61 72 31",结果getVar1;

第15个tag"01",长度"00 03",内容"28 29 49",结果()I;

第16个tag"01",长度"00 07",内容"73 65 74 56 61 72 31",结果setVar1;

第17个tag"01",长度"00 04",内容"28 49 29 56",结果(I)I;

第18个tag"01",长度"00 0A",内容"53 6f 75 72 63 65 46 69 6c 65",结果SourceFile;

第19个tag"01",长度"00 0E",内容"43 6C 61 73 73 54 65 73 74 2E 6A 61 76 61",结果ClassTest.java;

第20个tag"0C"表示12对象常量类型CONSTANT_NameAndType_info(tag U1;length U2;byte U2),表示字段或者方法的名称和描述,接下来4个字节"00 07 00 08",分别是常量池的第7、8项,结果为<init>:()V;

第21个tag"0C"与上一个类型一样,接下来4个字节"00 05 00 06",分别是常量池的第5、6项,结果为var1:I;

第22个tag"01",长度"00 1C",内容"63 6F 6D 2F 64 67 67 63 63 2F 74 65 73 74 2F 6C 65 69 2F 43 6C 61 73 73 54 65 73 74",结果com/dggcc/test/lei/Classtest;

第23个tag"01",长度"00 10",内容"6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74",结果java/lang/Object;

至此常量池数据分析完成,可以再返回去分析出前4个常量包含的内容,可以看出每个常量项要么直接表达出包含内容,要么通过指向常量池的一项或多项来组合成表达的结果,在idea通过javap -verbose com.dggcc.test.lei.ClassTest验证结果,结果如下图:

class文件的加载过程(真正理解class文件的常量池)(4)

总结

这里由于这个类比较简单,所以常量池较少,不过基本常见的结构都已经包含了,可以看出常量池中主要保存的是一些字符串,或者多个常量池字符串组合。到目前为止可能还看不出来常量池的作用,不过通过存储的内容也可以大概猜出,它基本包含了接下来为了表达用户代码所需要的字符串。

字节码文件的分析到目前为止已经过半,却还没有涉及到用户代码,不过常量池分析完成也就是用户代码编译的基础已经完成,接下来就可以继续分析用户代码了,下一篇文章继续!

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

class文件的加载过程(真正理解class文件的常量池)(5)

,
赞 (6)
打赏 微信扫一扫 微信扫一扫

相关推荐