It has been 689 days since the last update, the content of the article may be outdated.
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虚拟机规范
- Java语言规范:boolean类型只有两个取值:true和false,显然这两个符号是不能被虚拟机直接使用的
- Java虚拟机规范:boolean类型被映射成int类型,true被映射成1,false被映射成0
- 这个编码规则约束了Java字节码的具体实现
- 例如对于存储boolean数组的字节码,JVM需要保证实际存入的值为整数1或0
- 要求Java编译器也遵守这个编码规则,并且用整数相关的字节码来实现逻辑运算,以及boolean类型的条件跳转
- 因此,编译而成的class文件中,除了字段和入参外,基本看不出boolean类型的痕迹
基本类型
- 默认值看起来不一样,但在内存中都是0
- 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; float z2 = -0.0F; log.info("{}", floatToHexIntBits(z1)); log.info("{}", floatToHexIntBits(z2)); log.info("{}", z1 == z2);
|
两个Infinity
- 正无穷:任意正浮点数(不含+0.0F)除以**+0.0F**得到的值
- 负无穷:任意正浮点数(不含+0.0F)除以**-0.0F**得到的值
- 正无穷和负无穷都是有确切的值的,分别是0x7F800000和0xFF800000
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)); log.info("{}", floatToHexIntBits(Float.NEGATIVE_INFINITY));
|
NaN(Not-a-Number)
- NaN:**[0x7F800001, 0x7FFFFFFF] U [0xFF800001, 0xFFFFFFFF]**
- 标准NaN:**+0.0f/+0.0f,0x7FC00000**
- 除了**!=** 始终返回true之外,其他所有的比较结果都会返回false
1 2 3 4 5 6 7
| float f = 1.0F; log.info("{}", floatToHexIntBits(NaN)); log.info("{}", NaN < f); log.info("{}", NaN >= f); log.info("{}", NaN != f); log.info("{}", NaN == f); log.info("{}", NaN == NaN);
|
存储
- JVM每调用一个Java方法,都会创建一个栈帧
- 栈帧组成:局部变量表+操作数栈
- 局部变量表示广义的,包含实例方法的”this”指针和入参
- 局部变量表等价于一个数组,long和double 需要用2个数组单元来存储,其他基本类型和引用类型均占用1个数组单元
- boolean、byte、char、short、int、float和reference
- 32位HotSpot:在栈上占用4Bytes
- 64位HotSpot:在栈上占用8Bytes
- 这种情况仅存在于局部变量表中,并不会出现在存储在堆中的字段或者数组元素上
- 将一个int类型的值,存储到堆中的char类型字段时,相当于做了一次隐式的掩码操作(0xFFFFFFFF -> ‘\uFFFF’)
- 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());
unsafe.putByte(user, unsafe.objectFieldOffset(sexField), (byte) 3); log.info("{}", user.isSex()); } }
|
加载
- JVM的算数运算依赖于操作数栈,将堆中的boolean、byte、char以及short加载到操作数栈上,而后将栈上到值当做int类型来运算
- 对于boolean和char这两个无符号类型来说,加载伴随着零扩展
- 对于byte和short这两个有符号类型来说,加载伴随着符号扩展
参考资料
深入拆解Java虚拟机