详细分解classFile常量池(HelloWorld)

当一个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

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注