(三)Java 的基本类型

基本类型

Java 的基本类型包括整数类型 boolean 、byte、short、char、int 和 long,以及浮点类型 float 和 double。Java 的基本类型都有对应的值域和默认值。可以看到,byte、short、int、long、float 以及 double 的值域依次扩大,而且前面的值域被后面的值域所包含。因此,从前面的基本类型转换至后面的基本类型,无需强制转换。另外一点值得注意的是,尽管他们的默认值看起来不一样,但在内存中都是 0。

在这些基本类型中,boolean 和 char 是唯二的无符号类型。在不考虑违反规范的情况下,boolean 类型的取值范围是 0 或者 1。char 类型的取值范围则是 [0, 65535]。通常我们可以认定 char 类型的值为非负数。这种特性十分有用,比如说作为数组索引等。Java 的浮点类型采用 IEEE 754 浮点数格式。有关IEEE754 可以参考另外一篇文章(为什么双精度类型的0.1+0.7=0.7999999999999999)。

boolean Java 虚拟机中被映射为整数类型true 映射为 1,false 映射为 0。Java 代码中的逻辑运算以及条件跳转,都是用整数相关的字节码来实现的。以下引自jvms10:

Although the Java Virtual Machine defines a boolean type, it only provides very limited support for it. There are no Java Virtual Machine instructions solely dedicated to operations on boolean values. Instead, expressions in the Java programming language that operate on boolean values are compiled to use values of the Java Virtual Machine int data type.

 我们也可以查看编译后的字节码

public class Foo {
    public static void main(String[] args) {

        boolean flag=true;

        if (flag) {
            System.out.println("Hello,Java");
        }

        if (flag == true) {
            System.out.println("Hello,JVM");
        }
    }
}


 Code:
   stack=2, locals=2, args_size=1
      0: iconst_1
      1: istore_1
      2: iload_1
      3: ifeq          14
      6: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      9: ldc           #3                  // String Hello,Java
     11: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     14: iload_1
     15: iconst_1
     16: if_icmpne     27
     19: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     22: ldc           #5                  // String Hello,JVM
     24: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     27: return

 

 iconst_1 指令表示把 int 常量 1 压入操作栈:

 

Java 基本类型的大小

 虚拟机每调用一个 Java 方法,便会创建一个栈帧。为了方便理解,这里我只讨论供解释器使用的解释栈帧(interpreted frame)。这种栈帧有两个主要的组成部分,分别是局部变量区,以及字节码的操作数栈。这里的局部变量是广义的,除了普遍意义下的局部变量之外,它还包含实例方法的“this 指针”以及方法所接收的参数。

在 Java 虚拟机规范中,局部变量区等价于一个数组,并且可以用正整数来索引。除了 long、double 值需要用两个数组单元来存储之外,其他基本类型以及引用类型的值均占用一个数组单元。

也就是说,boolean、byte、char、short 这四种类型,在栈上占用的空间和 int 是一样的,和引用类型也是一样的。因此,在 32 位的 HotSpot 中,这些类型在栈上将占用 4 个字节;而在 64 位的 HotSpot 中,他们将占 8 个字节。

当然,这种情况仅存在于局部变量,而并不会出现在存储于堆中的字段或者数组元素上。对于 byte、char 以及 short 这三种类型的字段或者数组单元,它们在堆上占用的空间分别为一字节、两字节,以及两字节,也就是说,跟这些类型的值域相吻合。以下引自jvms10:

Each frame contains an array of variables known as its local variables. The length of the local variable array of a frame is determined at compile-time and supplied in the binary representation of a class or interface along with the code for the method associated with the frame .
A single local variable can hold a value of type boolean, byte, char, short, int, float, reference, or returnAddress. A pair of local variables can hold a value of type long or double.
A value of type long or type double occupies two consecutive local variables. Such a value may only be addressed using the lesser index. For example, a value of type double stored in the local variable array at index n actually occupies the local variables with indices n and n+1; however, the local variable at index n+1 cannot be loaded from. It can be stored into. However, doing so invalidates the contents of local variable n.

 因此,当我们将一个 int 类型的值,存储到这些类型的字段或数组时,相当于做了一次隐式的掩码操作。举例来说,当我们把 0xFFFFFFFF(-1)存储到一个声明为 char 类型的字段里时,由于该字段仅占两字节,所以高两位(Multibyte data items are always stored in big-endian order, where the high bytes come first.)的字节便会被截取掉,最终存入“\uFFFF”。

 

public class Foo {
  static boolean boolValue;
  public static void main(String[] args) {
    boolValue = true; // 将这个 true 替换为 2 或者 3,再看看打印结果
    // 赋值为2:都不输出
    // 赋值为3:都输出
    if (boolValue) System.out.println("Hello, Java!");
    if (boolValue == true) System.out.println("Hello, JVM!");
  }
}

我们把 十进制的数字 2 赋值给 flag 。十进制数字 2 转成二进制10 ,最低位为0。Java 虚拟机 ifeq 指令满足条件,则会跳过 "Hello World" 的输出。if_icmpne 指令结果为0, 跳过输出 "Hello JVM" ,所以数字2赋值给boolean变量,两个都不会打印,而数字3赋值给boolean 变量,两个都会打印出来。

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 来填充。

 

在 64 位的 Java 虚拟机中,对象头的标记字段占 64 位,而类型指针又占了 64 位。也就是说,每一个 Java 对象在内存中的额外开销就是 16 个字节。以 Integer 类为例,它仅有一个 int 类型的私有字段,占 4 个字节。因此,每一个 Integer 对象的额外内存开销至少是 400%。这也是为什么 Java 要引入基本类型的原因之一。

 

P.S. 本系列文章为学习出自郑雨迪的《深入拆解 Java 虚拟机》课程整理笔记。购买请扫描下方二维码: 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值