【JVM知识总结-1】JVM内存模型
【JVM知识总结-2】HotSpot虚拟机对象
【JVM知识总结-3】垃圾收集策略与算法
【JVM知识总结-4】HotSpot垃圾收集器
【JVM知识总结-5】内存分配与回收策略
【JVM知识总结-6】JVM性能调优
【JVM知识总结-7】类的文件结构
【JVM知识总结-8】类的加载时机
【JVM知识总结-9】类加载的过程
【JVM知识总结-10】类加载器

JVM的“无关性”

谈论JVM的无关性,主要有一下两个:

  • 平台无关性:任何操作系统都能运行Java代码
  • 语言无关性:JVM能运行除Java以外的其他代码

Java源代码首先需要使用Java编译期编译成.class文件,然后由JVM执行.class文件,从而程序开始运行。
JVM只认识.class文件,它不关心是何种语言生成了.class文件,只要.class文件符合JVM的规范就能运行。目前已经有JRuby、Jython、Scala等语言能够在JVM上运行。它们有各自的语法规则,不过它们的编译器都能将各自的源码编译成符合JVM规范的.class文件,从而能够借助JVM运行它们。

Java语言中的各种变量、关键字和运算符号的语义最终都由多条字节码命令组合而成的,因此字节码命令所能提供的语义描述能力肯定会比Java语言本身更加强大。因此,有一些Java语言本身无法有效支持的语言特性,不代表字节码本身无法有效支持。

Class文件结构

class文件是二进制文件,它的内容具有严格的规范,文件中没有任何的空格,全都是连续的0/1。Class文件中的所有内容被分为两种类型:无符号数、表。

  • 无符号数:无符号数表示Class文件中的值,这些值没有任何类型,但有不同的长度。u1,u2,u4,u8分别代表1/2/4/8字节的无符号数。
  • 表:由多个无符号数或者其他表作为数据项构成的复合数据累心。

Class文件具体由以下几个构成:

  • 魔数
  • 版本信息
  • 常量池
  • 访问标志
  • 类索引、父类索引、接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

魔数

Class文件的头4个字节称为魔数,用来表示这个Class文件的类型。
Class文件的魔数使用16进制表示的“CAFE BABE”

魔数相当于文件后缀名,只不过后缀名容易被修改,不安全,因此在Class文件中标识文件类型比较合适。

版本信息

紧接着魔数的4个字节是版本信息,5-6字节表示次版本号,7-8字节表示主版本号,它们表示当前Class文件中使用的是哪一个版本的JDK。
高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件。

常量池

版本信息之后就是常量池,常量池中存放两种类型的常量:

  • 字面值常量

字面值常量就是我们在程序中定义的字符串、被final修饰的值。

  • 符号引用

符号引用就是我们定义的各种名字:类和接口的全限定名、字段的名字和描述符、方法的名字和描述符。

常量池的特点

  • 常量池中常量数量不固定,因此常量池开头放置一个u2类型的无符号数,用来存储当前常量池的容量。
  • 常量池的每一项常量都是一个表,表示开始的第一位是一个u1类型的标志位(tag),代表当前这个常量属于哪种常量类型。

常量池中常量类型

类型tag描述
CONSTANT_utf8_info1UTF-8 编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16标识方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点

对于 CONSTANT_Class_info(此类型的常量代表一个类或者接口的符号引用),它的二维表结构如下:

类型名称数量
u1tag1
u2name_index1

tag是标志位,用于区分常量类型;name_index是一个索引值,它指向常量池中的一个CONSTANT_Utf8_info类型常量,此常量代表这个类(或接口)的全限定名,这里name_index值若为0x0002,也即是指向了常量池中的第二项常量。
CONSTANT_Utf8_info常量的结构如下:

类型名称数量
u1tag1
u2length1
u1byteslength

tag是当前常量的类型:length表示这个字符串的长度;bytes是这个字符串的内容(采用缩略的UTF8编码)

访问标志

在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个类是Class还是接口:是否定义为public类型;是否被abstract/final修饰。

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

类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合,Class文件中由这三项数据来确定类的继承关系。类索引用于确定这个类的全限定名,父类的索引用于确定父类的全限定类名。
由于Java不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。一个类可能实现了多个接口,因此用接口索引集合来描述。这个集合第一项为u2类型的数据,表示索引表的容量,接下来就是接口的名字索引。
类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过该常量总的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。

字段表集合

字段表集合存储本类涉及到的成员变量,包括实例变量和类变量,但不包括方法中的局部变量。
每个字段表只表示一个成员变量,本类中的所有成员变量构成了字段表集合。字段表结构如下:

类型名称数量说明
u2access_flags1字段的访问标志,与类稍有不同
u2name_index1字段名字的索引
u2descriptor_index1描述符,用于描述字段的数据类型。 基本数据类型用大写字母表示; 对象类型用“L 对象类型的全限定名”表示。
u2attributes_count1属性表集合的长度
u2attributesattributes_count属性表集合,用于存放属性的额外信息,如属性的值。
字段表集合中不会出现从父类(或接口)中继承而来的字段,但有可能出现原本Java代码中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。

方法表集合

方法表结构与属性表类似。
volatile关键字和transition关键字不能修饰方法,所以方法表的访问标志中没有ACC_VOLATILE和ACC_TRANSIENT标志。
方法表的属性表集合中有一张Code属性表,用于存储当前方法经过编译器编译后的字节码指令。

属性表集合

每一个属性对应一张属性表,属性表的结构如下:

类型名称数量
u2attribute_name_index1
u4attribute_length1
u1infoattribute_length

AllenYang
4 声望3 粉丝

We don't get to choose who we love