boolean类型

Java代码

1
2
3
4
5
6
7
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!");
}
}

编译运行

1
2
3
4
5
$ javac Foo.java

$ java Foo
Hello, Java!
Hello, JVM!

修改字节码运行

1
2
# jasm与javap的输出比较类似
$ java -cp ./asmtools.jar org.openjdk.asmtools.jdis.Main Foo.class > Foo.jasm.bak
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ tail -n 23 Foo.jasm.bak | head -n 21
public static Method main:"([Ljava/lang/String;)V"
stack 2 locals 2
{
iconst_1;
istore_1;
iload_1;
ifeq L14; # 出栈int,如果等于0时跳转;实际为1,无需跳转
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
ldc String "Hello, Java!";
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; # 出栈2个int,如果不相等时跳转;实际为1和1,无需跳转
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;
}

使用awk命令修改字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ awk 'NR==1,/iconst_1/{sub(/iconst_1/, "iconst_2")} 1' Foo.jasm.bak > Foo.jasm

$ tail -n 23 Foo.jasm.bak | head -n 21
public static Method main:"([Ljava/lang/String;)V"
stack 2 locals 2
{
iconst_2; # iconst_1 -> iconst_2
istore_1;
iload_1;
ifeq L14; # 出栈int,如果等于0时跳转;实际为1,无需跳转
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
ldc String "Hello, Java!";
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; # 出栈2个int,如果不相等时跳转;实际为1和2,需跳转
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;
}
1
2
3
4
$ java -cp ./asmtools.jar org.openjdk.asmtools.jasm.Main Foo.jasm

$ java Foo
Hello, Java!

Java语言规范+Java虚拟机规范

  1. Java语言规范:boolean类型只有两个取值:truefalse,显然这两个符号是不能被虚拟机直接使用的
  2. Java虚拟机规范:boolean类型被映射成int类型,true被映射成1,false被映射成0
    • 这个编码规则约束了Java字节码的具体实现
      • 例如对于存储boolean数组的字节码,JVM需要保证实际存入的值为整数1或0
    • 要求Java编译器也遵守这个编码规则,并且用整数相关的字节码来实现逻辑运算,以及boolean类型的条件跳转
      • 因此,编译而成的class文件中,除了字段入参外,基本看不出boolean类型的痕迹

基本类型

  1. 默认值看起来不一样,但在内存中都是0
  2. boolean和char是无符号类型,通常我们可以认定char类型是非负数,可以作为数组索引

浮点数

两个0

1
2
3
private static String floatToHexIntBits(float f) {
return Integer.toHexString(Float.floatToIntBits(f));
}
1
2
3
4
5
float z1 = +0.0F; // +0.0F
float z2 = -0.0F; // -0.0F
log.info("{}", floatToHexIntBits(z1)); // 0
log.info("{}", floatToHexIntBits(z2)); // 0x80000000
log.info("{}", z1 == z2); // 两个0对应的内存数值不同,但+0.0F == -0.0F

两个Infinity

  1. 正无穷:任意正浮点数(不含+0.0F)除以**+0.0F**得到的值
  2. 负无穷:任意正浮点数(不含+0.0F)除以**-0.0F**得到的值
  3. 正无穷和负无穷都是有确切的值的,分别是0x7F8000000xFF800000
1
2
3
4
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
log.info("{}", floatToHexIntBits(Float.POSITIVE_INFINITY)); // 0x7F800000
log.info("{}", floatToHexIntBits(Float.NEGATIVE_INFINITY)); // 0XFF800000

NaN(Not-a-Number)

  1. NaN:**[0x7F800001, 0x7FFFFFFF] U [0xFF800001, 0xFFFFFFFF]**
  2. 标准NaN:**+0.0f/+0.0f,0x7FC00000**
  3. 除了**!=** 始终返回true之外,其他所有的比较结果都会返回false
1
2
3
4
5
6
7
float f = 1.0F;
log.info("{}", floatToHexIntBits(NaN)); // 0x7FC00000
log.info("{}", NaN < f); // false
log.info("{}", NaN >= f); // false
log.info("{}", NaN != f); // true
log.info("{}", NaN == f); // false
log.info("{}", NaN == NaN); // false

存储

  1. JVM每调用一个Java方法,都会创建一个栈帧
  2. 栈帧组成:局部变量表+操作数栈
    • 局部变量表示广义的,包含实例方法的”this”指针和入参
  3. 局部变量表等价于一个数组longdouble 需要用2个数组单元来存储,其他基本类型和引用类型均占用1个数组单元
    • boolean、byte、char、short、int、float和reference
      • 32位HotSpot:在栈上占用4Bytes
      • 64位HotSpot:在栈上占用8Bytes
    • 这种情况仅存在于局部变量表中,并不会出现在存储在堆中的字段或者数组元素上
  4. 将一个int类型的值,存储到堆中的char类型字段时,相当于做了一次隐式的掩码操作(0xFFFFFFFF -> ‘\uFFFF’)
  5. boolean数组直接用byte数组来实现
    • 为了保证堆中的boolean值是合法的,HotSpot在存储时进行显式的掩码操作,只取最后一位的值存入boolean字段或数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Slf4j
@Data
public class User {
private boolean sex;

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);

User user = new User();
Field sexField = User.class.getDeclaredField("sex");

unsafe.putByte(user, unsafe.objectFieldOffset(sexField), (byte) 2);
log.info("{}", user.isSex()); // 10 -> 0 , false

unsafe.putByte(user, unsafe.objectFieldOffset(sexField), (byte) 3);
log.info("{}", user.isSex()); // 11 -> 1 , true
}
}

加载

  1. JVM的算数运算依赖于操作数栈,将堆中的boolean、byte、char以及short加载到操作数栈上,而后将栈上到值当做int类型来运算
  2. 对于booleanchar这两个无符号类型来说,加载伴随着零扩展
  3. 对于byteshort这两个有符号类型来说,加载伴随着符号扩展

参考资料

深入拆解Java虚拟机