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_info | 1 | UTF-8 编码的字符串 |
CONSTANT_Integer_info | 3 | 整型字面量 |
CONSTANT_Float_info | 4 | 浮点型字面量 |
CONSTANT_Long_info | 5 | 长整型字面量 |
CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
CONSTANT_Class_info | 7 | 类或接口的符号援用 |
CONSTANT_String_info | 8 | 字符串类型字面量 |
CONSTANT_Fieldref_info | 9 | 字段的符号援用 |
CONSTANT_Methodref_info | 10 | 类中办法的符号援用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中办法的符号援用 |
CONSTANT_NameAndType_info | 12 | 字段或办法的符号援用 |
CONSTANT_MethodHandle_info | 15 | 示意办法句柄 |
CONSTANT_MethodType_info | 16 | 标识办法类型 |
CONSTANT_InvokeDynamic_info | 18 | 示意一个动静办法调用点 |
对于 CONSTANT_Class_info(此类型的常量代表一个类或者接口的符号援用),它的二维表构造如下:
类型 | 名称 | 数量 |
---|---|---|
u1 | tag | 1 |
u2 | name_index | 1 |
tag 是标记位,用于辨别常量类型;name_index 是一个索引值,它指向常量池中的一个 CONSTANT_Utf8_info 类型常量,此常量代表这个类(或接口)的全限定名,这里 name_index 值若为 0x0002,也即是指向了常量池中的第二项常量。
CONSTANT_Utf8_info 常量的构造如下:
类型 | 名称 | 数量 |
---|---|---|
u1 | tag | 1 |
u2 | length | 1 |
u1 | bytes | length |
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 类型的常量中的全限定名字符串。
字段表汇合
字段表汇合存储本类波及到的成员变量,包含实例变量和类变量,但不包含办法中的局部变量。
每个字段表只示意一个成员变量,本类中的所有成员变量形成了字段表汇合。字段表构造如下:
类型 | 名称 | 数量 | 阐明 |
---|---|---|---|
u2 | access_flags | 1 | 字段的拜访标记,与类稍有不同 |
u2 | name_index | 1 | 字段名字的索引 |
u2 | descriptor_index | 1 | 描述符,用于形容字段的数据类型。根本数据类型用大写字母示意;对象类型用“L 对象类型的全限定名”示意。 |
u2 | attributes_count | 1 | 属性表汇合的长度 |
u2 | attributes | attributes_count | 属性表汇合,用于寄存属性的额定信息,如属性的值。 |
字段表汇合中不会呈现从父类(或接口)中继承而来的字段,但有可能呈现本来 Java 代码中不存在的字段,譬如在内部类中为了放弃对外部类的拜访性,会主动增加指向外部类实例的字段。
办法表汇合
办法表构造与属性表相似。
volatile 关键字和 transition 关键字不能润饰办法,所以办法表的拜访标记中没有 ACC_VOLATILE 和 ACC_TRANSIENT 标记。
办法表的属性表汇合中有一张 Code 属性表,用于存储以后办法通过编译器编译后的字节码指令。
属性表汇合
每一个属性对应一张属性表,属性表的构造如下:
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |