Java基础:你是否了解KVM的常量池

原野新之助22

原野新之助22

2016-02-19 20:08

只要你有一台电脑或者手机,都能关注图老师为大家精心推荐的Java基础:你是否了解KVM的常量池,手机电脑控们准备好了吗?一起看过来吧!

  在class文件中,“常量池”是最复杂也最值得关注的内容。

  Java是一种动态连接的语言,常量池的作用非常重要,常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值还,还包含一些以文本形式出现的符号引用,比如:

  类和接口的全限定名;

  字段的名称和描述符;

  方法和名称和描述符。

  在C语言中,假如一个程序要调用其它库中的函数,在连接时,该函数在库中的位置(即相对于库文件开头的偏移量)会被写在程序中,在运行时,直接去这个地址调用函数;

  而在Java语言中不是这样,一切都是动态的。编译时,假如发现对其它类方法的调用或者对其它类字段的引用的话,记录进class文件中的,只能是一个文本形式的符号引用,在连接过程中,虚拟机根据这个文本信息去查找对应的方法或字段。

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  所以,与Java语言中的所谓“常量”不同,class文件中的“常量”内容很非富,这些常量集中在class中的一个区域存放,一个紧接着一个,这里就称为“常量池”。

  常量池由多条“常量池项”组成,每一个常量池项又由两部分组成,这里分别称为“常量池项头”和“常量池项体”。

  常量池项头表明常量池项的类型,常量池项共分为11种类型,分别为:

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  常量池项类型
   值
   说明
   
  CONSTANT_Utf8
   1
   UTF-8编码的Unicode字符串
   
  CONSTANT_Integer
   3
   int型常量
   
  CONSTANT_Float
   4
   Float型常量
   
  CONSTANT_Long
   5
   Long型常量
   
  CONSTANT_Double
   6
   double型常量
   
  CONSTANT_Class
   7
   对一个class的符号引用
   
  CONSTANT_String
   8
   String型常量
   
  CONSTANT_Fieldref
   9
   对一个字段的符号引用
   
  CONSTANT_Methodref
   10
   对一个类方法的符号引用
   
  CONSTANT_InterfaceMedthodref
   11
   对一个接口方法的符号引用
   
  CONSTANT_NameAndType
   12
   对名称和类型的符号引用

  常量池项体中存放的就是对应的常量数据,比如各种数值型的常量或者字符串等等。

  以下介绍kvm中的常量池是如何组织起来的。

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  数据结构:

  在KVM的头文件kvm/vmcommon/h/pool.h中,有以下对常量池项类型的定义:

  #define CONSTANT_Utf8                       1
  #define CONSTANT_Integer                    3
  #define CONSTANT_Float                      4
  #define CONSTANT_Long                       5
  #define CONSTANT_Double                     6
  #define CONSTANT_Class                      7
  #define CONSTANT_String                     8


  
   #define CONSTANT_Fieldref                   9
  #define CONSTANT_Methodref                  10
  #define CONSTANT_InterfaceMethodref    11
  #define CONSTANT_NameAndType            12
   以及常量池项体结构的定义:

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  union constantPoolEntryStrUCt {
      struct {
          unsigned short classIndex;
          unsigned short nameTypeIndex;
      }               method;  /* Also used by Fields */
      CLASS           clazz;
      INTERNED_STRING_INSTANCE String;
      cell           *cache;   /* Either clazz or String */
      cell            integer;
      long            length;
      NameTypeKey     nameTypeKey;
      NameKey         nameKey;
      UString         ustring;
  };
  class文件中,常量池项有很多种类,每一个常量池项的大小都不同,而对于常量池的使用又是如此之多,最好能够使用数组来索引,这样可以提高效率,所以KVM里使用union来代表一个常池项,union的每一项是常量池项的一种可能的数据类型,这样每一项都有了相同的大小,可以构造数组。

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  显然,这个数组就将是常量池的核心内容,那么这个数组放在哪里呢?就在下面这个结构中:

  struct constantPoolStruct {
      union constantPoolEntryStruct entries[1];
  };
  这就是常量池。这个常量池的设计很有意思:

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  1、这个结构体中只有一个指针,指向一个常量池项体数组,数组中元素的个数是常量池项数+1,数组中的第一项(即序号为0的那一项)不是实际的常量池项体,而是存放了常量池项的数目,即表明了数组中接下来的元素数。要取得数组的长度信息,只有一个办法,就是读数组的第一个元素,为不造成空指针错误,所以constantPoolStruct在定义的时候就要保证数组的第0个元素必须存在,所以上面的entries在定义时就被指定为长度为1的数组。

  单纯从数据结构的设计角度来看,我认为constantPoolStruct的设计并不是很清楚,使用数组的第一个无素来表示数组的长度多少一点显得混乱,明明可以在constantPoolStruct的结构里增加一个变量来表明数组长度,这样不是更清楚吗?之所以这样做,我想也是与class文件中常量池的设计惯例有关。在class文件中, constant_pool紧跟在constant_pool_count之后,而constant_pool_count = constant_pool中实际的项数+1,相当于constant_pool_count也把自己当成了常量池中的第一项。

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  由此可见,KVM的常量池设计与class文件如出一辙。

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  2、常量池项体以一个union来表示,而union不带有自身类型的信息,如何知道一个常量池项的类型呢?

  在一个class文件的常量池被载入后,生成了constantPoolStruct结构体的实例,在其中constantPoolEntryStruct数组的最后一项之后,一定会跟随一个字节数组,这个数组中的每一个字节就是一个“常量池项头”,长度与实际的常量池项数相同,即constant_pool_count-1,在这个字节中就指明了相应常量池项的类型。

  程序实现:

  构造常量池的代码段主要在kvm/vmcommon/src/loader.c的loadConstantPool()函数中,函数原形如下:

  static POINTERLIST

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  loadConstantPool(FILEPOINTER_HANDLE ClassFileH, INSTANCE_CLASS CurrentClass);

  两个参数分别为类文件的句柄以及当前被载入类的指针。

  这个函数的总体流程如下:

  1- 循环读取文件中常量池中所有项,把,把各项内容存入临时数组RowPool中;(L649~L740)

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  2- 计算常量池所占空间大小(以constantPoolEntryStruct枚举体数计),并申请常量池空间;(L742~L757)

  3- 循环读取暂存在RowPool中的常量信息,为常量池赋值。

  其中第2步值得一看,记算空间大小的那一行如下:

  

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  int tableSize = numberOfEntries + ((numberOfEntries + (4 - 1)) 2);
  
   一个constantPoolEntryStruct枚举体的大小为4,前面讲过,在constantPoolEntryStruct数组的后要跟有一个字节数组来存放常量池项的类型信息,即每一个constantPoolEntryStruct要对应1个字节的常量池项头,所以当以constantPoolEntryStruct枚举体数为单位给常量池项头数组申请空间时,需要向4字节对齐,每多1~4个常量池项头,就要多申请一个constantPoolEntryStruct。这一句就是这个意思。

  loadConstantPool函数执行过程中,会把新生成的常量池指针赋给CurrentClass-constPool,这样,这个类实例中就有完整的常量池了。


展开更多 50%)
分享

猜你喜欢

Java基础:你是否了解KVM的常量池

编程语言 网络编程
Java基础:你是否了解KVM的常量池

深入java线程池的使用详解

编程语言 网络编程
深入java线程池的使用详解

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

创建Java中的线程池

编程语言 网络编程
创建Java中的线程池

Java Interface 是常量存放的最佳地点吗?

编程语言 网络编程
Java Interface 是常量存放的最佳地点吗?

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

PS调色基础 让你彻底了解直方图

电脑网络
PS调色基础 让你彻底了解直方图

关于Java初学者需要了解的几个基础问题

编程语言 网络编程
关于Java初学者需要了解的几个基础问题

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

在Foxmail中直接查看HTML格式邮件

在Foxmail中直接查看HTML格式邮件

生活无奈伤感的QQ分组设计_已经无力去诉说

生活无奈伤感的QQ分组设计_已经无力去诉说
下拉加载更多内容 ↓