36

基本类型

 5 years ago
source link: http://zhongmingmao.me/2018/12/15/jvm-basic-native-type/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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虚拟机规范

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

基本类型

RJRFBbE.png!web

  1. 默认值看起来不一样,但在内存中都是 0
  2. 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

  1. 正无穷: 任意正浮点数 (不含+0.0F)除以 +0.0F 得到的值
  2. 负无穷: 任意正浮点数 (不含+0.0F)除以 -0.0F 得到的值
  3. 正无穷和负无穷都是有 确切 的值的,分别是 0x7F8000000xFF800000
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
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字段或数组
@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 这两个 有符号 类型来说,加载伴随着 符号扩展

转载请注明出处:http://zhongmingmao.me/2018/12/15/jvm-basic-native-type/

访问原文「基本类型」获取最佳阅读体验并参与讨论


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK