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

2016-02-19 20:08 3 1 收藏

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

【 tulaoshi.com - 编程语言 】

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

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

  类和接口的全限定名;

  字段的名称和描述符;

  方法和名称和描述符。

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

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

  

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

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

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

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

  

  常量池项类型
   值
   说明
   
  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
   对名称和类型的符号引用

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

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

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

  

  数据结构:

  在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
   以及常量池项体结构的定义:

  

  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的每一项是常量池项的一种可能的数据类型,这样每一项都有了相同的大小,可以构造数组。

  

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

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

  

  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也把自己当成了常量池中的第一项。

  

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

  

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

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

  程序实现:

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

  static POINTERLIST

  

  loadConstantPool(FILEPOINTER_HANDLE ClassFileH, INSTANCE_CLASS CurrentClass);

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

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

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

  

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

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

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

  

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

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


来源:https://www.tulaoshi.com/n/20160219/1622852.html

延伸阅读
在不同系统之间交换信息的一大障碍是如何在精确交换和格式化数据方面取得一致。Java Message Service( Java消息服务,简称JMS)通过提供一种与J2EE应用程序或传统系统交互的方法部分的解决了这个问题。 !-- frame contents -- !-- /frame contents -- JMS的通用接口集合以异步方式发送或接收消息。异步方式接收...
1.前言: 当我们学习过了Java中的基本语法,并且熟悉java的面向对象基础以后,我们就可以开始简单的Swing程序的设计,用过Vb的朋友可能会被它的简单的设计用户界面方法所吸引,只需要拖几个控件到窗体上,!-- frame contents -- !-- /frame contents --为每个空件编写event就可以简单的实现界面设计.但是强大的java也不比vb逊色.同样...
由于本文旨在探讨Java"异常机制"的深层原理,因此关于"异常"的使用方法都不做详细说明。首先看一段非常熟悉的用于打开一个文件的C程序段: FILE *fp; fp=fopen(filename,"rw"); if(fp==NULL){ printf("cannot open file"); exit(0); } 在这段程序中,if条件语句中的一段用来...
如何了解乳汁是否充足 许多妈妈对自己的乳汁是否足够,缺乏判断力,现在就教你一些判断的方法。 1.看婴儿的小便。如果纯母乳喂养的孩子每天小便在六次及以上,尿色无色或淡黄色,尿量能将尿布浸透,说明奶足够。 2.看孩子的体重。每日或间隔几日为孩子称一下体重,如果孩子的体重每星期增加在150克以上,说明奶足够。还可以将所称的体重标在...
标签: 催乳
如何了解乳汁是否充足 许多妈妈对自己的乳汁是否足够,缺乏判断力,现在就教你一些判断的方法。 1.看婴儿的小便。如果纯母乳喂养的孩子每天小便在六次及以上,尿色无色或淡黄色,尿量能将尿布浸透,说明奶足够。 2.看孩子的体重。每日或间隔几日为孩子称一下体重,如果孩子的体重每星期增加在150克以上,说明奶足够。还可以将所称的体重标在...

经验教程

314

收藏

92
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部