最近在看JVM方面的东西,其中看到关于基本类型方面的一些讲解,在早之前未曾留意,今天这里记录一下。
在java语言规范中boolean类型只有true和false,但是jvm不能直接使用true和false。在jvm规范中,boolean类型被映射为int,true被映射为1,false被映射为0。也这样一来,在编译而成的字节码文件中,除了字段和传入参数外,基本上看不到boolean的痕迹了。
java code:
public class Test {
public static void main(String[] args) throws Exception {
boolean flag = true;
if(flag) {
System.out.println("Hello World");
}
if(flag == true) {
System.out.println("Hello JVM");
}
}
}
-----------------------------------------------------------------------------------
javap -v Test.class
public static Method main:"([Ljava/lang/String;)V"
throws java/lang/Exception
stack 2 locals 2
{
iconst_1; // 对应 true
istore_1;
iload_1;
ifeq L14;
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
ldc String "Hello World";
invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
L14: stack_frame_type append;
locals_map int;
iload_1;
iconst_1;
if_icmpne L27;
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
ldc String "Hello JVM";
invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
L27: stack_frame_type same;
return;
}
除了boolean之外,java还有byte,char,short,int,long,float,double。在这些类型中boolean和char是无符号类型,在不违反约束的情况下,boolean的取值范围是0或1,char的取值范围是[0,65535]。
由于boolean和其他类型有点不同,这里先验证一下,对于boolean变量除了0和1之外,像2,3等这样的值也是可以直接操作。
这里使用asmtools来进行验证,需要jdk8及以上。
$ echo 'public class Test {
public static void main(String[] args) throws Exception {
boolean flag = true;
if(flag) {
System.out.println("Hello World");
}
if(flag == true) {
System.out.println("Hello JVM");
}
}
}' Test.java
$ javac Test.java
$ java -jar asmtools.jar jdis Test.class > Test.jsam.1
// 下面的命令是将boolean赋值为3
$ awk 'NR==1,/iconst_1/{sub(/iconst_1/,"iconst_3")} 1' Test.jasm.1 > Test.jasm
# java -jar asmtools.jar jasm Test.jasm
$ java Test
输出 Hello World
------------------------------------------------
上面最终的Test字节码如下:
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Exceptions:
throws java.lang.Exception
Code:
stack=2, locals=2, args_size=1
0: iconst_3 // 源文件编译后这里是1,通过awk 修改为3
1: istore_1
2: iload_1
3: ifeq 14 // 操作数栈顶等于0在跳转到14行
6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #2 // String Hello World
11: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: iload_1
15: iconst_1
16: if_icmpne 27 // 两个int变量不相等就跳转到27行
19: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
22: ldc #1 // String Hello JVM
24: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 14
locals = [ int ]
frame_type = 12 /* same */
}
在jvm中,栈帧中的两个主要组成部分:局部变量区和操作数栈。局部变量区等价于一个数组,可通过正整数来索引。除了long和double值需要两个数组单元,其他基本类型及引用类型均是占用一个单元。也即是说boolean,byte,char,short在栈上占用的空间和int是一样的,和引用类型也一样。
这种情况仅存在于局部变量,而不会出现在存储于堆区的字段或数组单元上,在堆上而言,上述四种类型占用的空间与其值域相吻合。
对于byte,boolean,char,short而言,从堆区加载和存储这些字段或数组单元时分别会进行 符合扩展 和 掩码操作,这里boolean或boolean数组来说 比较特殊。
在HotSpot中boolean占用一个字节(堆区中用byte映射boolean),对于基本类型的运算几乎都是在栈上进行的,上面说到,在栈中,boolean是占用和int一样的,因此对boolean类型(堆区中:字段或数组)写入时需要进行掩码操作,也就是取最后一位的值存入boolean字段或数组中。比如说,栈中的值是2,在存入到boolean字段或数组中的值是0,栈中是3,则存入的是1。
因此对于上面用int验证boolean的列子中,将 flag 变量提到main方法外面作为字段时,
使用iconst_2时,将不会打印出任务字符,使用iconst_3时,会打印两条字符。
The end!!!!