类文件结构学习笔记

时间:2021-05-17 14:03:50

“Java一次编译,处处运行”解释

     各种不同平台的虚拟机与所有平台都统一使用的存储格式——字节码(ByteCode)是构成平台无关性的基石。 
实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java虚拟机不和包括Java在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其它辅助信息。 
      任何一门功能性语言都可以表示为一个能被Java虚拟机所接受的有效的Class文件。作为一个通用的、机器无关的执行平台,任何其他语言的实现者都可以将Java虚拟机作为语言的产品交付媒介。只需要将代码编译成为Class文件交给虚拟机运行,虚拟机并不关心Class的来源是何种语言。

常量池

      常量池可以理解为Class文件之中的资源仓库,是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时还是在Class文件中第一个出现的表类型数据项目。 
Class文件结构中只有常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,从0开始计数。 
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了一下三类常量:

  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符

       Java代码在运行Javac编译的时候,是在虚拟机加载Class文件的时候进行动态连接。也就是在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

从JVM层面解释为什么不能根据返回值重载方法?

       在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名中,因此Java语言里是无法仅仅依靠返回值的不同来对一个已有方法进行重载。但是在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法也可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个Class文件中的。

请注意: 
       在Java中分别存在字节码(JVM)层面的方法特征签名和Java代码层面的方法特征签名,Java代码的方法特征签名只包括了方法名称、参数顺序及参数类型,而字节码的方法特征签名方法返回值以及受查异常表,请根据上下文语境做以区分。

属性表集合的ConstantValue属性

 
       ConstantValue属性的作用是同志虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)才可以使用这项属性。类似“int x=369”(实例变量)和“static int x=369”(类变量)这样的变量定义在Java程序中非常常见,但是JVM虚拟机对这两种变量赋值的方式和时刻都有所不同。 
对于非static类型的变量(也就是实例变量)的赋值是在实例构造器方法中进行的; 
对于static类型的变量(类变量),则有两种方式可以选择:在类构造器方法中或者使用ConstantValue属性。 
      目前Sun Javac编译器的选择是:若同时使用final和static来修饰一个变量(可称为“常量”),并且这个变量的数据类型是基本类型或者java.lang.String的话,就生成ConstantValue属性来进行初始化,若该变量没有被final修饰,或者并非基本类型及字符串,则将会选择在方法中进行初始化。

      ConstantValue的属性值只能限于基本类型和String,是因为此属性的属性值只是一个常量池的索引号,由于Class文件格式的常量类型中只有与基本属性和字符串相对应的字面量,所以就是ConstantValue属性想支持别的类型也无能为力。