深入拆解Java虚拟机学习笔记2
在Java中,我们引入了八个基本类型来支持数值计算,我们首先需要来了解下基本类型在Java中的具体实现
Boolean类型
Java语言规范中,Boolean类型的值只有两种可能,分别用true和false来表示
在Java虚拟机规范中,Boolean类型被映射成为了int类型,具体来说就是true被映射为了1,而false被映射为了0
Java的基本类型
类型 | 值域 | 默认值 | 虚拟机内部符号 |
---|---|---|---|
boolean | {true,false} | false | Z |
byte | [-128,127] | 0 | B |
short | [-32768,32767] | 0 | S |
char | [0,65535] | ‘\u0000’ | C |
int | [-2^31,2^31-1] | 0 | I |
long | [-2^63,2^63-1] | 0L | J |
float | ~[-3.4E38,3.4E38] | +0.0F | F |
double | ~[-1.8E308,1.8E308] | +0.0D | D |
Java的基本类型都有对应的值域和默认值,byte、short、int、long、float、double的值域依次扩大,前面的值域被后面包含,因此从前面的类型转换到后面的类型无需强制转换
这些基本类型中,boolean和char是无符号类型,boolean类型的取值范围是0或者1,char类型的取值范围是[0,65535],通常我们可以认定char类型的值为负数
那问题来了,声明为byte、char以及short的局部变量是否能够存储超过他们取值范围的数呢?答案是可以的
Java的基本类型的大小
Java虚拟机每调用一个Java方法就会创建一个栈桢。我们先讨论仅提供给解释器使用的解释栈桢(interpreted frame)
这种栈桢具有两个主要的组成部分,分别是局部变量区以及字节码的操作数栈,这里的局部变量是广义的,除了普遍意义下的局部变量意外,还包含实例方法的this 指针以及方法所接收的参数
在Java虚拟机规范中,局部变量等价于一个数组,并且可以用正整数来索引。除了long、double值需要用两个数组单元来存储以外,其他基本类型以及引用类型的值均占用了一个数组单元
也就是说,boolean、 byte、char、 short这四种类型,在栈上占用的空间和 int是一样的,和引用类型也是一样的。因此,在32位的HotSpot中,这些类型在栈上将占用4个字节;而在64位的HotSpot中,他们将占8个字节。
当然,这种情况仅存在于局部变量,而并不会出现在存储于堆中的字段或者数组元素上。对于byte、char 以及short 这三种类型的字段或者数组单元,它们在堆上占用的空间分别为一字节、两字节以及两字节,也就是说,跟这些类型的值域相吻合。
因此,当我们将一个 int 类型型的值,存储到这些类型的字段或数组时,相当于做了一次隐式的掩码操作。举例来说,当我们把OXFFFFFFFF (-1)存储到一个声明为 char类型的字段里时,由于该字段仅占两字节,所以高两位的字节便会被截取掉,最终存入”UFFFF”。boolean 字段和 boolean 数组则比较特殊。在HotSpot中, boolean字段占用一字节,而 boolean 数组则直接用byte数组来实现。为了保证堆中的boolean 值是合法的,HotSpot 在存储时显式地进行掩码操作,也就是说,只取最后一位的值存入boolean 字段或数组中。
讲完了存储,现在我来讲讲加载。Java 虚拟机的算数运算几乎全部依赖于操作数栈。也就是说,我们需要将堆中的boolean、 byte、char 以及short加载到操作数栈上面,而后将栈上的值当成int类型来运算。
对于 boolean、char 这两个无符号类型来说,加载伴随着零扩展。举个例子, char的大小为两个字节。在加载时char的值会被复制到 int类型的低二字节,而高二字节则会用0来填充。
对于 byte、short 这两个类型来说,加载伴随着符号扩展。举个例子,short 的大小为两个字节。在加载时short 的值同样会被复制到 int 类型的低二字节。如果该short值为非负数,即最高位为0,那么该int 类型的值的高二字节会用0来填充,否则用1来填充。