JVMJvm使用深入理解Java虚拟机

[JVM]理解Class文件(1):手动解析常量池

2017-02-07  本文已影响408人  伤口不该结疤

引言

class文件是Java虚拟机提供的语言无关性的基石,如果要深入地了解虚拟机,那么必须学习class文件是如何解析的。

Java虚拟机提供的语言无关性

而常量池是class文件结构中第一个出现的表类型数据项目,也是Class文件中最烦琐的数据,理解了常量池的解析过程,Class文件其它字段的分析也就迎刃而解了。

Class文件结构

ClassFile结构

Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,包含两种数据类型

每一个Class文件对应于一个如下所示的ClassFile结构体:

ClassFile {
    u4 magic; 
    u2 minor_version; 
    u2 major_version; 
    u2 constant_pool_count; 
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags; 
    u2 this_class; 
    u2 super_class; 
    u2 interfaces_count; 
    u2 interfaces[interfaces_count]; 
    u2 fields_count; 
    field_info fields[fields_count]; 
    u2 methods_count; 
    method_info methods[methods_count]; 
    u2 attributes_count; 
    attribute_info attributes[attributes_count]; 
}

示例java代码

将下面这段代码编译生成class,用作后续分析。

public class TestClass {
    private static final String TEST = "test string";
}

示例class文件

将生成的class文件用UltraEdit打开,可以清楚地看到Java编译后生成的字节码,我们要解析的内容也就是这些字节码。

00000000h: CA FE BA BE 00 00 00 33 00 12 0A 00 03 00 0E 07 ; 
00000010h: 00 0F 07 00 10 01 00 04 54 45 53 54 01 00 12 4C ; 
00000020h: 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 ; 
00000030h: 3B 01 00 0D 43 6F 6E 73 74 61 6E 74 56 61 6C 75 ; 
00000040h: 65 08 00 11 01 00 06 3C 69 6E 69 74 3E 01 00 03 ; 
00000050h: 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E ; 
00000060h: 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 0A 53 ;
00000070h: 6F 75 72 63 65 46 69 6C 65 01 00 0E 54 65 73 74 ;
00000080h: 43 6C 61 73 73 2E 6A 61 76 61 0C 00 08 00 09 01 ; 
00000090h: 00 09 54 65 73 74 43 6C 61 73 73 01 00 10 6A 61 ; 
000000a0h: 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 ;  
000000b0h: 0B 74 65 73 74 20 73 74 72 69 6E 67 00 21 00 02 ;  
000000c0h: 00 03 00 00 00 01 00 1A 00 04 00 05 00 01 00 06 ;  
000000d0h: 00 00 00 02 00 07 00 01 00 01 00 08 00 09 00 01 ;  
000000e0h: 00 0A 00 00 00 1D 00 01 00 01 00 00 00 05 2A B7 ; 
000000f0h: 00 01 B1 00 00 00 01 00 0B 00 00 00 06 00 01 00 ; 
00000100h: 00 00 02 00 01 00 0C 00 00 00 02 00 0D          ; 

魔数和版本号

在解析常量池之前,先来看下Class文件中最开始的两个字段

由于文件扩展名是可以随意修改的,因此,很多文件都会使用魔数作为文件标识,如gif或者jpeg等在头文件中都存在魔数。

class文件版本号

常量池

存放内容

常量池存放的内容如下:

常量池数据结构

要解析常量池的数据,先得看下它的数据结构,这些数据结构都是在 Java虚拟机规范(Java SE 7).pdf里面定义的。

在class文件中,常量池入口紧接在主版本后之后, constant_pool_count表示常量的个数,constant_pool是存放的是所有常量信息的表项,存放的个数为constant_pool_count - 1constant_pool可以将理解为一个数组,其中的每一项代表一个常量。

由“图1.魔数、版本号和常量池计数器”所示constant_pool_count值为18,由于常量池容量计数是从1开始的,0用作表示“不引用任何对象”,因此,常量个数为17个(constant_pool_count - 1)

ClassFile {
    ... ...
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    ... ...
}

cp_info { 
    u1 tag; 
    u1 info[]; 
}

其中,tag表示常量的类型info[]代表tag类型所属的表项

tag值对应的类型如下表:

常量池tag

以开头的class文件为例,紧接着常量池计数器后的"0A"是第一个常量的tag,"0A"对应十进制值为10,结合上表,得出第一个常量类型为CONSTANT_Methodref

0A.png

CONSTANT_Methodref对应的数据结构为CONSTANT_Methodref_info,也就是说constant_pool[1]中的u1 info[]CONSTANT_Methodref_info

CONSTANT_Methodref_info { 
    u1 tag; 
    u2 class_index; 
    u2 name_and_type_index; 
}

根据上面的描述,我们接着解析"0A"后面的字段,由CONSTANT_Methodref_info数据结构可知,"0A"往后再取2个u2类型数据的长度就截止了,其中class_index = 3, name_and_type_index = 14

假设我们已经将后面的常量池字段全部解析完成了(文末附有整个常量池解析结构),可以得出constant_pool[3] = "java/lang/Object" ,constant_pool[14] = " "<init>":()V "

因此,第一个常量constant_pool[1]为:java/lang/Object."<init>":()V

CONSTANT_Methodref_info

类型为CONSTANT_Class,对应结构体为CONSTANT_Class_info,由此知解析的第二个常量的数据段为07 00 0F

CONSTANT_Class_info {
     u1 tag; 
     u2 name_index; 
}

最后得到结果:该常量是一个类的名称,类名存放在constant_pool[15] = "TestClass" , 即constant_pool[2] = TestClass

解析第二个常量

按照上面的方法,可以手动解析出剩下的15个常量。我们也可以通过javap完成对class文件的解析,javap命令使用方法和输入结果如下:

D:\TestClass>javap -verbose TestClass
Classfile /D:/TestClass/TestClass.class
  Last modified 2017-2-7; size 269 bytes
  MD5 checksum b2a3f1078d18eba859aaeac73bd7621d
  Compiled from "TestClass.java"
public class TestClass
  SourceFile: "TestClass.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#14         //  java/lang/Object."<init>":()V
   #2 = Class              #15            //  TestClass
   #3 = Class              #16            //  java/lang/Object
   #4 = Utf8               TEST
   #5 = Utf8               Ljava/lang/String;
   #6 = Utf8               ConstantValue
   #7 = String             #17            //  test string
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               SourceFile
  #13 = Utf8               TestClass.java
  #14 = NameAndType        #8:#9          //  "<init>":()V
  #15 = Utf8               TestClass
  #16 = Utf8               java/lang/Object
  #17 = Utf8               test string

参考

上一篇下一篇

猜你喜欢

热点阅读