class文件常量池可以说是class的资源库,可以说它包含所有class文件需要的字符
再看class文件结构再次引入class文件结构图如下图:
在上一篇文章中分析了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文件中表示,项目表示每个常量类型的结构,tag表明常量类型,byte表示常量项包含的具体内容。第一项CONSTANT_Utf8_info表达的是一个字符串,包含一个length结构表示这个字符串有多少个字节组成。还有一些项目包含index结构,表示他是通过引用常量池的其他常量项来表达(下一节分析具体示例能更加理解)。类型表示各个常量项各个结构暂用的字节数。
常量池示例分析还是按照上一篇文章的class文件继续分析,文件内容图如下图:
上一篇文章我们分析了前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验证结果,结果如下图:
这里由于这个类比较简单,所以常量池较少,不过基本常见的结构都已经包含了,可以看出常量池中主要保存的是一些字符串,或者多个常量池字符串组合。到目前为止可能还看不出来常量池的作用,不过通过存储的内容也可以大概猜出,它基本包含了接下来为了表达用户代码所需要的字符串。
字节码文件的分析到目前为止已经过半,却还没有涉及到用户代码,不过常量池分析完成也就是用户代码编译的基础已经完成,接下来就可以继续分析用户代码了,下一篇文章继续!
Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!