当一个java文件被javac编译之后,就可以成为一个通用的class文件格式。我们之前说JVM里面的运行期常量池,主要也就是说的从classFile中读取的常量信息。作为JVM来说,它并没有对类,接口,实例等有一个内部的数据结构,所有的指令的引用信息均来自于常量池中。
Java Virtual Machine instructions do not rely on the run-time layout of classes, interfaces, class instances, or arrays. Instead, instructions refer to symbolic information in the constant_pool table.
本文自:http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html 4.4节
这里,我们有一个很简单的HelloWorld例子,如下所示:
public class T { public static void main(String args[]) { String s = "abcdefghijk"; System.out.println(s); } }
采用指令:javac -g:none T.java进行编译。编译之后产生的class如下所示:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 00000000 CA FE BA BE 00 00 00 33 00 1A 0A 00 06 00 0C 08 3 00000010 00 0D 09 00 0E 00 0F 0A 00 10 00 11 07 00 12 07 00000020 00 13 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 <init> () 00000030 56 01 00 04 43 6F 64 65 01 00 04 6D 61 69 6E 01 V Code main 00000040 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 ([Ljava/lang/S 00000050 74 72 69 6E 67 3B 29 56 0C 00 07 00 08 01 00 0B tring;)V 00000060 61 62 63 64 65 66 67 68 69 6A 6B 07 00 14 0C 00 abcdefghijk 00000070 15 00 16 07 00 17 0C 00 18 00 19 01 00 01 54 01 T 00000080 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 java/lang/Obje 00000090 63 74 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 53 ct java/lang/S 000000A0 79 73 74 65 6D 01 00 03 6F 75 74 01 00 15 4C 6A ystem out Lj 000000B0 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 ava/io/PrintStre 000000C0 61 6D 3B 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 am; java/io/Pr 000000D0 69 6E 74 53 74 72 65 61 6D 01 00 07 70 72 69 6E intStream prin 000000E0 74 6C 6E 01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E tln (Ljava/lan 000000F0 67 2F 53 74 72 69 6E 67 3B 29 56 00 21 00 05 00 g/String;)V ! 00000100 06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 00000110 09 00 00 00 11 00 01 00 01 00 00 00 05 2A B7 00 *? 00000120 01 B1 00 00 00 00 00 09 00 0A 00 0B 00 01 00 09 ? 00000130 00 00 00 17 00 02 00 02 00 00 00 0B 12 02 4C B2 L? 00000140 00 03 2B B6 00 04 B1 00 00 00 00 00 00 +? ?
本文是描述如何从这个classFile中详细的找出相应的常量池信息,从文件本身描述的信息中手动地找出相应的内部结构,以方便进一步掌握class的结构形式,以方便了解JVM规范及组织。
常量池个数
从00006 000007可以看出,我们是以版本51,即jdk1.7来进行编译的。
从00008 00009可以看出,在这个编译版本为,常量数是1A,即26。实际的常量个数为26-1,即25个。
常量结构(General)
cp_info { u1 tag; u1 info[]; }
如上所示,一个常量以1个类型码(u1表示1个无类型字节)开头,后接特定类型格式的数据信息。
常量类型对照码
Constant Type | Value |
---|---|
CONSTANT_Class (类) |
7 |
CONSTANT_Fieldref(字段) |
9 |
CONSTANT_Methodref(方法) |
10 |
CONSTANT_InterfaceMethodref (接口方法) |
11 |
CONSTANT_String(字符串) |
8 |
CONSTANT_Integer(整形) |
3 |
CONSTANT_Float(浮点数) |
4 |
CONSTANT_Long(长整形) |
5 |
CONSTANT_Double(双精度浮点数) |
6 |
CONSTANT_NameAndType(名称和类型对,如方法名描述) |
12 |
CONSTANT_Utf8(具体表示字符串的内部结构) |
1 |
CONSTANT_MethodHandle(新增,方法句柄) |
15 |
CONSTANT_MethodType(新增,方法类型) |
16 |
CONSTANT_InvokeDynamic(新增动态调用句柄) |
18 |
类CONSTANT_Class_info
CONSTANT_Class_info { u1 tag; u2 name_index; }
即类型码为7 后接2个字节的字符串描述串(UTF8串)索引的位置
字段CONSTANT_Fieldref_info
,方法CONSTANT_Methodref_info
,接口方法CONSTANT_InterfaceMethodref_info
这3个结构是一样的,除类型码不一样,后面的结构基本一致,如下所示:
CONSTANT_XXX_info { u1 tag; u2 class_index; u2 name_and_type_index; }
1字节类型码,2字节类信息的索引值,2位置名称类对的索引位置
字符串CONSTANT_String_info
CONSTANT_String_info { u1 tag; u2 string_index; }
在class内部,字符串常量被认为是一种类似基本类型的信息。
格式为1字节类型码值8 + 2字节的utf8串的索引值.
整数CONSTANT_Integer_info,浮点数
CONSTANT_Float_info
CONSTANT_XXXX_info { u1 tag; u4 bytes; }
整数和浮点数均由4个字节即32位来描述,所以这里的结构为类型码3/4 接4个字节的具体描述值(对于整形,可以理解为将4个字节的16进制拼在一起,即表示相应的信息)
长整形CONSTANT_Long_info 双精度浮点数
CONSTANT_Double_info
CONSTANT_XXX_info { u1 tag; u4 high_bytes; u4 low_bytes; }
1字节的类型码5/6,接4个字节的高位数,再接4个字节的低位数。所以存储位置一共为64位。
名称类型对 CONSTANT_NameAndType_info
CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
1字节类型码12,接2字节的名称串索引位置,接2位置的描述串索引位置。如 void print(int i),名称即为print,描述即为方法的描述(I;)V
UTF8字符串内部表示 CONSTANT_Utf8_info
CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
这就是描述所有字符串信息的内部描述信息,除数字之外的描述均要先要存在相应的描述串,然后直接引用这个描述串地址即可。这样就可以将所有的字符串统一处理,然后其它结构直接引用即可。包括String常量,也是直接引用的utf8串描述信息。
从名称上即可以看出,这个串是以UTF-8进行编码的,所有的字符均被编码为utf-8字节信息进行存放。
结构为类型码1,接整2个字节的字节串长度(是所有字节的长度,而非串长度),后接这个长度值的编码信息.
描述完这些信息,那么,再回头看我们的classFile,我们按照这个规则详细的解析这25个常量信息。
#1 0000A至0000E 0A 00 06 00 0C
0A即方法描述信息 其名称为指向索引为6的UTF8串 描述为指向索引为12的名称对
#2 0000F至00101 08 00 0D
08即string字符串 其指向索引为13位置的UTF8串
#3 00102至00106 09 00 0E 00 0F
09即字段描述信息 其名称指向索引值为14位置的UTF8串 描述指向索引值为15位置的UTF8串
#4 00107至0010B 0A 00 10 00 11
0A即方法描述信息 其名称指向索引值为16的UTF8串 描述指向索引值为17位置的UTF8串
#5 0010C至0010E 07 00 12
07即类描述信息 其名称指向索引值为18位置的UTF8串
#6 0010F至00201 07 00 13
07即类描述信息 其名称指向索引值为19位置的UTF8串
#7 00202至0020A 01 00 06 3C 69 6E 69 74 3E
01即UTF8串 其长度为6 具体的值即为<init>
#8 0020B至00300 01 00 03 28 29 56
01即UTF8串 其长度为3 具体的值即为()V
#9 00301至00307 01 00 04 43 6F 64 65
01即UTF8串 其长度为4 具体的值即为Code
#10 00308至0030E 01 00 04 6D 61 69 6E
01即UTF8串 其长度为4 具体的值即为main
#11 0030F至00501 01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
01即UTF8串 其长度为22 具体的值即为([Ljava/lang/String;)V
#10和#11可以理解为一个名称对的2个部分,即方法main(String[] args)的描述名称对
#12 00502至0050C 0C 00 07 00 08
0C即名称对 其名称部分为索引值07的UTF8串 描述部分为索引值08的UTF8串
这即是描述了方法 <init> ()V即构建方法的信息
而#01引用#12,即01处描述的方法就是一个默认的构造方法,即public void T(){}(默认实现)
#13 0050D至0060A 01 00 0B 61 62 63 64 65 66 67 68 69 6A 6B
01即UTF8串 其长度为11 具体的值即为abcdefghijk
#14 0060B至0060D 07 00 14
07即类信息 其名称对描述为索引值20的名称对
#15 0060E至00702 0C 00 15 00 16
0C即名称对 其名称部分为索引值21的UTF8串 描述部分为索引值22的UTF8串
#16 00703至07705 07 00 17
07即类信息 其名称对描述为索引值23的名称对
#17 00706至0070A 0C 00 18 00 19
0C即名称对 其名称部分为索引值24的UTF8串 描述部分为索引值25的UTF8串
#18 0070B至0070E 01 00 01 54
01即UTF8串 其长度为1 描述的值即为T
#19 0070F至00901 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
01即UTF8串 其长度为16 描述的值为java/lang/Object
#20 00902至00A04 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 64 6D
01即UTF8串 其长度为16 描述的值为java/lang/System
#21 00A05至00A0A 01 00 03 6F 75 74
01即UTF8串 其长度为3 描述的值为out
#22 00A0B至00C02 01 00 15 4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B
01即UTF8串 其长度为21 描述的值为Ljava/io/PrintStream;
从前者的名称对描述可知 #21和#22共同组成了一个描述信息,从下文看,这里描述的是一个字段信息。即类System的out这个字段
#23 00C03至00D08 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D
01即UTF8串 其长度为19 描述的值为java/io/PrintStream
#24 00D09至00E02 01 00 07 70 72 69 6E 74 6C 6E
01即UTF8串 其长度为7 描述的值为println
#25 00E03至00F0A 01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
01即UTF8串 其长度为21 描述的值为(Ljava/lang/String;)V
至此,所有的常量信息均已解析完毕。这些常量信息,有的是直接可以看出使用范围的。如文中的String,而有些则在classFile后面格式中的filed信息和method中被使用,这里就不再详细描述。我们使用命令javap -v T.class反编译相应的信息,看是否和我们解析的一致。相应的信息如下所示:
转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/201402200001.html