内容简介:使用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: 类型转换和类型断言
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
程式之美-微軟技術面試心得
編程之美小 / 悅知文化 / 2008.06.20 / 490元
書內容分為以下幾個部分: ▓ 遊戲之樂:從遊戲和其他有趣問題出發,化繁為簡,分析總結。 ▓ 數字之魅:程式設計的過程實際上就是和數字及字元打交道的過程。這一部分收集了一些這方面的有趣探討。 ▓ 結構之法:彙集了常見的對字串、鏈表、佇列,以及樹進行操作的題目。 ▓ 數學之趣:列舉了一些不需要寫具體程式的數學問題,鍛煉讀者的抽象思考能力。 ▓ 書中絕大部分題目都提供了詳細......一起来看看 《程式之美-微軟技術面試心得》 这本书的介绍吧!