深入理解Java虚拟机(七)——类文件结构

Java的无关性

由于计算机领域中有很多操作系统和硬件平台同时在竞争,所以,很多编程语言的程序设计会与其运行的平台和操作系统产生耦合,这样就大大增加了程序员的工作,为了适应不同的平台,需要修改很多代码。这样,具有无关性特征的Java语言就开始受欢迎了。

平台的无关性:由于有可以运行在不同硬件平台和操作系统上的Java虚拟机,而这些虚拟机都可以载入执行与平台无关的字节码,从而实现了一次编译,到处运行。所有虚拟机都支持一种程序存储格式——字节码,是构成平台无关性的基石

语言的无关性:不同平台上的Java虚拟机可以运行Java以外的语言。语言无关性的基础是虚拟机和字节码存储格式。

为什么Java虚拟机可以运行其他语言的代码呢?

Java代码的运行过程是这样的:首先通过javac编译器将程序编译程类文件,然后通过虚拟机运行类文件即可。那么,这样就将虚拟机与Java语言很好地解耦了,只要能将任何语言编译成符合虚拟机要求的字节码类文件,那样就都能放到Java虚拟机上运行。为此需要做的,就是根据Java虚拟机规范编写相应语言的编译器即可。

Class类文件的结构

class文件时一组以8个子节为基础单位的二进制流,各个数据项目紧凑排列,没有分隔符,空间利用率高。文件中采用一种类似C语言结构体的结构存储数据,只有两类数据类型:无符号数

  • 无符号数
    属于基本的数据类型,里面利用u1、u2、u4、u8来分别标识1个子节、2个子节、4个子节8个子节的无符号数,可以用来描述数字、索引引用、字符串等。


  • 表是由无符号数或者其他表组成的复合数据类型。Class文件本质也是一张表。

class文件的组织结构

  1. 魔数
  2. 本文件的版本信息
  3. 常量池
  4. 访问标志
  5. 类索引
  6. 父类索引
  7. 接口索引集合
  8. 字段表集合
  9. 方法表集合
    10.属性表

在这里插入图片描述

1魔数

Class文件的头四个子节被称为魔数,用来标识这个是不是Java虚拟机要的class文件。

这四个子节为:0xCAFEBABE

2版本信息

紧接着魔数的4个子节就是文件的版本号,标识其所使用的JDK版本。第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)。

版本只可以向下兼容。

3常量池

常量池是什么

常量池用于存放class文件的资源,其中包括:

  • 字面量:文本字符串和被声明为final的常量。
  • 复合引用:就是程序中定义的各种名称和定义类型包括:
    • 被模块导出或者开放的包
    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符
    • 方法句柄和方法类型
    • 动态调用点和动态常量

常量池的特点

  • 常量池是与其他项目关联最多的数据
  • 使用空间最大的项目之一
  • 第一个出现表类型的数据项目
  • 其中的数据数量不固定,入口需要放置一个u2类型的数据标识常量池的容量
  • 每个常量由一张二维的表组成,除了记录常量的值,还包括其相关信息

常量类型

每一项常量都是一个表,现在一共有17中类型的常量,所以就有17种不同结构的表。

这12种表都有一个共同的特点,第一个位是u1类型的标志位,标识这个常量属于什么类型。
在这里插入图片描述

为什么Java中定义类和变量名的长度必须小于64K?

因为class文件中标识类和变量名等名称都是属于符号引用,都是通过常量池中的一个CONSTANT_Utf8_info类型的常量来描述的,而这个类型的常量通过2个子节来存储字符串的长度,最大值是65535,所以这些名字的最大长度是64k。

4访问标志

常量池结束后的两个子节,用于标识一类或接口层次的访问信息。

两个子节十六位,目前只使用了八位,没使用的一律为零。

访问信息包括:

  • 这个Class是类还是接口;
  • 是否定义为public类型;
  • 是否定义为abstract类型;
  • 是类的话,是否为final。

在这里插入图片描述

5类索引、父类索引、接口索引集合

用于表示当前类的名字、父类的名字和接口们的名字。这三项数据类用于确定该类型的继承关系。

类索引和父类索引都是两个字节,而接口索引集合就是一组两个字节的数据集合。

类索引是如何找到类的详细信息的呢?
类索引和父类索引用两个u2类型的索引值表示,它们各自能够指向一个类型为CONSTANT_Class_info的类描述符常量,然后通过这个常量中的索引值,可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。

接口索引是如何找到其实现接口的详细信息的?
接口索引集合入口第一项就是一个u2类型的数据,作为一个接口计数器,数值代表实现接口数量。如果该类型没有实现任何接口,则计数器的值为0。通过遍历计数器的值,产生一个偏移量,就可以在之后的索引表中查询相应的一个接口索引值,然后通过这个索引值,在常量表中查询对于的接口信息。

6字段表集合

字段表用于描述接口或者类中声明的变量。字段表的集合用于描述本类涉及到的所有成员变量,包括实例变量和类变量,到那时不包括方法中的局部变量。

字段表结构
在这里插入图片描述

  1. access_flags:表示访问标志,类似与类的访问标志。其中每个位的含义如下表表示
    在这里插入图片描述
  2. name_index和descriptor_index:是对常量池的引用,分别表示字段的简单名称以及字段和方法的描述符号。
  3. atrributes_count:表示表集合的长度。
  4. attributes:表属性的集合,用于存储额外的信息。

什么是简单名称和全限定名

简单名称就是没有类型和参数修饰的方法或者字段名称。

全限定名称就是包含类的路径包含信息,例如“org/fenixsoft/clazz/TestClass”。

什么是描述符

成员变量和方法都有自己的描述符。
对于字段而言,描述符是字段的数据类型信息;
对于方法而言,描述符是字段的数据类型、参数列表、返回值信息。

字段表集合的要点

  1. 不会列出从父类或者父接口继承而来的字段。
  2. 可能出先原本代码中不存在的字段。例如编译器为添加指向外部类实例的字段,以满足对内部类访问外部类。
  3. Java程序中不能存在两个相同名字的字段名,但是对于class文件的格式来说,只要两个字段的描述符有区别,字段名相同也是合法的。

7方法表的集合

用于存储类中的方法。
所有的方法以二维表的形式存储,每张表表示一个方法。

对方法的描述和对字段的描述如出一辙。

方法表集合的要点

  1. 如果没有重写父类的的方法,那么本类集合中就不会出现来自父类的方法信息。
  2. 需要重载一个方法时,除了需要与原方法具有相同的简单名称之外,还需要有一个不同的特征签名。特征签名是指这个方法的各个参数在常量池中的字段符号引用的集合。因为返回值不在特征签名之中,所以,Java语言不能仅仅依靠返回值来对一个方法进行重载。而在class文件格式中就是可以的。

8 属性表的集合

用于描述程序运行过程中的相关属性。内容过多且繁杂,这里不做叙述。

原文地址:https://www.cnblogs.com/lippon/p/14117684.html