内容简介:使用awk命令修改字节码转载请注明出处:http://zhongmingmao.me/2018/12/15/jvm-basic-native-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!"); } }
编译运行
$ javac Foo.java $ java Foo Hello, Java! Hello, JVM!
修改字节码运行
# jasm与javap的输出比较类似 $ java -cp ./asmtools.jar org.openjdk.asmtools.jdis.Main Foo.class > Foo.jasm.bak
$ 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命令修改字节码
$ 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; }
$ 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类型的痕迹
- 这个编码规则约束了 Java字节码的具体实现
基本类型
- 默认值看起来不一样,但在内存中都是 0
- boolean和char是 无符号类型 ,通常我们可以认定char类型是非负数,可以作为数组索引
浮点数
两个0
private static String floatToHexIntBits(float f) { return Integer.toHexString(Float.floatToIntBits(f)); }
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
- 正无穷: 任意正浮点数 (不含+0.0F)除以 +0.0F 得到的值
- 负无穷: 任意正浮点数 (不含+0.0F)除以 -0.0F 得到的值
- 正无穷和负无穷都是有 确切 的值的,分别是 0x7F800000 和 0xFF800000
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)
- NaN: [0x7F800001, 0x7FFFFFFF] U [0xFF800001, 0xFFFFFFFF]
- 标准NaN: +0.0f/+0.0f,0x7FC00000
- 除了 != 始终返回 true 之外,其他所有的比较结果都会返回false
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
存储
- JVM每调用一个 Java方法 ,都会创建一个 栈帧
- 栈帧组成: 局部变量表 + 操作数栈
- 局部变量表示广义的,包含实例方法的”this”指针和入参
- 局部变量表等价于一个 数组 , long 和 double 需要用 2个 数组单元来存储,其他基本类型和引用类型均占用1个数组单元
- boolean、byte、char、short、int、float和reference
- 32位HotSpot:在栈上占用 4Bytes
- 64位HotSpot:在栈上占用 8Bytes
- 这种情况 仅存在于局部变量表 中,并不会出现在存储在堆中的字段或者数组元素上
- boolean、byte、char、short、int、float和reference
- 将一个 int类型 的值,存储到 堆中的char 类型字段时,相当于做了一次 隐式的掩码操作 (0xFFFFFFFF -> ‘\uFFFF’)
- boolean数组直接用byte数组来实现
- 为了保证堆中的boolean值是合法的,HotSpot在存储时进行 显式的掩码操作 ,只取最后一位的值存入boolean字段或数组
@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 } }
加载
- JVM的算数运算依赖于操作数栈,将堆中的boolean、byte、char以及short加载到操作数栈上,而后将栈上到值 当做int类型 来运算
- 对于 boolean 和 char 这两个 无符号 类型来说,加载伴随着 零扩展
- 对于 byte 和 short 这两个 有符号 类型来说,加载伴随着 符号扩展
转载请注明出处:http://zhongmingmao.me/2018/12/15/jvm-basic-native-type/
访问原文「基本类型」获取最佳阅读体验并参与讨论
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- golang的值类型,指针类型和引用类型&值传递&指针传递
- Scala 类型的类型(三)
- Scala 类型的类型(二)
- Scala 类型的类型(三)
- Scala 类型的类型(二)
- golang: 类型转换和类型断言
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Ruby on Rails实践之路
沃哈 / 科学 / 2010-5 / 48.00元
《Ruby on Rails实践之路:写给PHP和Java开发者的书》内容简介:Ruby on Rails是基于MVC模式的Web框架,用于开发基于数据库的Web应用。Ruby on Rails中内含了所需的Web服务器WEBrick。该框架配置的数据库除了缺省的MySQL外,还可以是Oracle、SQL Server等其他数据库。《Ruby on Rails实践之路:写给PHP和Java开发者的......一起来看看 《Ruby on Rails实践之路》 这本书的介绍吧!