xiwenAndlejian/my-blog

JVM 学习笔记(一)Java 虚拟机结构(1)

Closed this issue · 0 comments

文章内容大部分出自:Oracle JVM

Java 虚拟机结构(1)

主要内容:

  • class 文件简介
  • Java 的数据类型

class 文件

由 Java 虚拟机执行的编译代码,使用独立于硬件和操作系统的二进制格式表示,通常存储在文件中,称为 class 文件格式。

class 文件格式定义类或接口,包含他们的详细信息。

这里需要注意的是 class 文件中存储的是二进制格式的数据,关于它的格式以及如何解析将在后文学习。

数据类型

与 Java 编程语言一样,Java 虚拟机也可以使用两种类型:基本类型(primitive types)引用类型(reference types)。相应的,有两种值可以存储变量中,作为参数传递,由方法返回,并对其进行操作:原始值(primitive values)引用值(reference values)

Java 虚拟机期望几乎所有类型检查都在运行时之前完成,通常由编译器完成,而不必由 Java 虚拟机本身完成。原始类型的值不需要被标记或以其他方式可检查以在运行时确定它们的类型,或者与引用类型的值区分。相反,Java 虚拟机的指令集使用旨在对特定类型的值进行操作的指令来区分其操作数类型。例如,iaddladdfadddadd都是 Java 虚拟机指令,它们添加两个数值并生成数值结果,但每个指令都专门用于其操作数类型:intlongfloatdouble。有关 Java 虚拟机指令集中类型支持的摘要,请参见§2.11.1。

Java 虚拟机包含对对象的显式支持。对象是动态分配的类实例数组。对对象的引用被认为具有 Java 虚拟机类型引用。**类型引用的值可以被认为是指向对象的指针。**可能存在多个对象的引用。始终通过类型引用的值操作,传递和测试对象。

  • Java 虚拟机指令集中指令的操作数类型由首字母确定:iadd操作int类型数据,fadd操作float类型数据。

对象是动态分配的类示例或数组

关于这句话,类的实例 = 对象,这个很好理解,那么为什么数组也包含在对象中?

先来执行一段代码:

public void testDataType() {
    int[]      ints     = new int[]{};
    double[][] doubles  = new double[][]{};
    Object[]   objects  = new Object[]{};
    Object[]   objects2 = new Object[][]{};

    System.out.println(ints.getClass());
    System.out.println(doubles.getClass());
    System.out.println(objects.getClass());
    System.out.println(objects2.getClass());
}

输出:

class [I
class [[D
class [Ljava.lang.Object;
class [[Ljava.lang.Object;

可以看出数组确实是对象,并且它的格式也很有规律,根据数组中元素和维度不同可以分为:

  • 一维:
    • 基本数据类型(eg:intlong...):[类型缩写
    • Java 类:[L类全限定名;
  • 二维:在一维的基础上前缀增加一个[
  • ...
  • N维:前缀包含 N 个[,其余内容同一维。

原始类型和值

Java 虚拟机支持的原始数据类型:数字类型,boolean类型和returnAddress类型。

数字类型(numeric)

数字类型由整数类型浮点类型组成。

整数类型:

  • byte:其值为 8 位有符号二进制补码整数,其默认值为零(1 Byte),范围[-2^7^, 2^7^-1] 即 [-128, 127]
  • short:其值为 16 位有符号二进制补码整数,其默认值为零(2 Byte),范围[-2^15^, 2^15^-1] 即 [-32768, 32767]
  • int:其值为 32 位有符号二进制补码整数,其默认值为零(4 Byte),范围[-2^31^, 2^31^-1] 即 [-2147483648, 2147483647]
  • long:其值为 64 位有符号二进制补码整数,其默认值为零(8 Byte),范围[-2^63^, 2^63^-1] 即 [-9223372036854775808, 9223372036854775807]
  • char:其值为 16 位==无==符号二进制补码整数,其默认值为零(2 Byte),范围[0, 2^16^-1] 即 [0, 65535]

有符号类型由于要使用 1 位(首位)二进制数,作为符号位,因此所能表达的最大值约等于无符号类型的一半。

⚠️ 字节(Byte):1 字节 = 8 比特(bit)。

🆕顺便一提,端口号的范围也是:[0, 65535],可以推出端口号也是由 2 字节表示的。

浮点类型:

  • float:其值是float集的元素,或者,如果支持,则为float-extended-exponent值集,其默认值为正零
  • double:其值是double值集的元素,或者,如果支持,则为double-extended-exponent值集,其默认值为正零

布尔类型(boolean)

boolean 类型:布尔类型的值编码真值truefalse默认值false

返回地址类型(returnAddress)

returnAddress类型的值 是指向 Java 虚拟机指令的操作码的指针。在原始类型中,只有returnAddress类型与 Java 编程语言类型没有直接关联

浮点类型,集值和值

浮点类型是 floatdouble,它在概念上与 32 位单精度和 64 位双精度格式 IEEE 754 值和操作相关联。

IEEE 754 标准不仅包括正和负符号幅度数,还包括正负零,正无穷大和负无穷大,以及特殊的非数字值(以下简称为NaN)。NaN值用于表示某些无效操作的结果,例如将零除以零。

Java虚拟机的每个实现都需要支持两组标准的浮点值,称为浮点值集双值集。此外,Java 虚拟机的实现可以选择支持两个扩展指数浮点值集中的一个或两个,称为 float-extended-exponent值集double-extended-exponent值集。在某些情况下,可以使用这些扩展指数值集代替标准值集来表示类型的值 floatdouble

⚠️下文为懵逼的内容

任何浮点值集的有限非零值都可以表示为 s·m·2^(e-N+1),其中 s 为+1或-1,m 为小于 2N 的正整数,e是Emin = - ((2^(K-1))-2)和Emax = (2^(K-1))-1 之间的整数,其中 N 和 K 是取决于值集的参数。有些值可以多种方式表示在这种形式中;例如,假设值集中的值 v 可能使用 s,m 和 e 的某些值以此形式表示,那么如果发生了 m 均匀且 e 小于 2^(K-1),则可以将 m 减半并且将 e 增加 1 以产生相同值 v 的第二表示。如果 m ≥ 2^(N-1),则以这种形式表示称为归一化;否则该表示被称为非规范化。如果值集中的值不能以m ≥ 2^N-1^的方式表示,则该值被称为非正规化值,因为它没有归一化表示。

下表总结了对两个必需和两个可选浮点值集的参数 N 和 K (以及导出参数 Emin 和 Emax)的约束

参数 浮点 浮点扩展指数 双精度 双精度扩展指数
ñ 24 24 53 53
ķ 8 ≥11 11 ≥15
Emax +127 ≥+ 1023 +1023 ≥+ 16383
Emin -126 ≤-1022 -1022 ≤-16382

NaNs外,浮点值集的值是有序的。当从最小到最大排列时,它们是负无穷大,负有限值,正负零,正有限值,和正无穷大。

浮点正零和浮点负零比较相等。但还有其他操作可以区分,eg:将1.0 除以 0.0 会产生正无穷大,但将 1.0 除以 -0.0 会产生负无穷大。

NaNs是无序的,因此如果其操作数中的一个或两个都是 NaN,则数值比较和数值相等的测试具有值false。特别是,当且仅当值为NaN时,对值自身的数值相等性的测试具有值false。如果任一操作数是NaN,则对数值不等式的测试值为true。

public void testNaN() {
    float f1 = 0.0f / 0.0f;
    float f2 = 0.0f / 0.0f;

    System.out.println(f1 == f1);// false
    System.out.println(f1 == f2);// false
    System.out.println(f1 != 0.0f);// true
    System.out.println(f1 != 1);// true
}

简单来说:

  • 表达式:A == B,其中 A 或 B 为NaN,则此表达式值为 false
  • NaNNaN(true)
  • 表达式:A != B,其中 A 或 B 为NaN,则此表达式值为 true

returnAddress 类型和值

returnAddress类型是使用 Java 虚拟机的 jsr、ret、jsr_w 指令。

returnAddress 类型的值是指向Java虚拟机指令的操作码的指针。

与数字基元类型不同,该returnAddress类型不对应于任何Java编程语言类型,并且不能由正在运行的程序修改。

boolean 类型

没有专门用于 boolean 值操作的 Java 虚拟机指令。

Java 编程语言中对boolean值进行操作的表达式将 被编译为使用 Java 虚拟机int数据类型的值。

Java虚拟机直接支持布尔数组。它的 newarray 指令可以创建布尔数组。使用字节数组指令baload和bastore访问和修改boolean类型的数组。

在Oracle的Java虚拟机实现中,Java编程语言中的布尔数组被编码为Java虚拟机字节(Byte)数组,每个布尔元素使用 8 位(bit),即 1 字节。

Java虚拟机boolean 使用1来表示true0表示数组组件false。如果boolean编译器将Java编程语言 值映射到Java虚拟机类型的值 int,则编译器必须使用相同的编码。

引用类型和值

有三种引用类型:类类型、数组类型和接口类型。对应的值分别是动态创建的实例、数组或实例数组、实现接口的数组。

数组类型由具有单个维度的 组件类型 (其长度不是由类型给出)组成。数组类型的组件类型本身可以是数组类型。

⚠️数组类型的元素类型必须是:基本类型、类类型或接口类型。

一个引用值可以是专用的空引用(null),没有对象的引用。该null引用最初没有运行时类型,但可以转换为任何类型。

⚠️引用类型的默认值为:null

Java 虚拟机规范没有强制要求编码 null 的具体值。