JVM原理(四):字节码

一、概述

   java源码编译后都会生成字节码,字节码是JVM执行代码的中间形态,到运行时jvm会将字节码文件读取到内存中并翻译为机器可识别执行的机器码。JVM字节码是一种基于栈的指令集架构(Stack-based Instruction Set Architecture)。每个字节码指令都会在JVM上执行一系列的操作,如加载、存储、运算、跳转等。

二、字节码生成

   对于源码的编译,jdk提供了javac 工具,对于单文件的字节码生成,先使用javac 生成,然后可以使用 javap 进行查看。
在这里插入图片描述在这里插入图片描述

$ javap -c PropertyValue
警告: 文件 .\PropertyValue.class 不包含类 PropertyValue
Compiled from "PropertyValue.java"
public class com.resean.spring.beans.factory.config.PropertyValue {
  public com.resean.spring.beans.factory.config.PropertyValue(java.lang.String, java.lang.Object, java.lang.String, boolean);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: aload_1
       6: putfield      #7                  // Field name:Ljava/lang/String;
       9: aload_0
      10: aload_2
      11: putfield      #13                 // Field value:Ljava/lang/Object;
      14: aload_0
      15: aload_3
      16: putfield      #17                 // Field type:Ljava/lang/String;
      19: aload_0
      20: iload         4
      22: putfield      #20                 // Field isRef:Z
      25: return

  public com.resean.spring.beans.factory.config.PropertyValue(java.lang.String, java.lang.Object);
    Code:
       0: aload_0
       1: ldc           #24                 // String
       3: aload_2
       4: aload_1
       5: iconst_0
       6: invokespecial #26                 // Method "<init>":(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Z)V
       9: return

  public java.lang.String getName();
    Code:
       0: aload_0
       1: getfield      #7                  // Field name:Ljava/lang/String;
       4: areturn

  public java.lang.Object getValue();
    Code:
       0: aload_0
       1: getfield      #13                 // Field value:Ljava/lang/Object;
       4: areturn

  public java.lang.String getType();
    Code:
       0: aload_0
       1: getfield      #17                 // Field type:Ljava/lang/String;
       4: areturn

  public boolean isRef();
    Code:
       0: aload_0
       1: getfield      #20                 // Field isRef:Z
       4: ireturn
}

   在实际的项目中,对于代码的打包,由maven或者gradel 这些打包工具完成。后续将仔细讲讲 maven的构建原理。

三、字节码指令

   字节码在jvm中运行实际上是根据指令集一行行读取并进行操作的,了解字节码指令集可以帮助我们了解jvm的运行机制及编写高效的代码。
   字节码指令集主要包括操作码和操作数两部份,指令集主要分为7大类:

  • 加载与存储指令
       加载与存储指令是用得最频繁的命令,其格式为
    • 加载:xload_n:表示局部变量表的槽位; x表示操作码助记符,表明是哪种数据类型。
    • 存储:xstore_n:表示局部变量表的槽位; x表示操作码助记符,表明是哪种数据类型。
      在这里插入图片描述
  • 算术指令
        算术指令是用于两个操作栈中值的特定运算,分为两种类型,整型运算和浮点型运算。
        需要注意的是,数据运算可能会导致溢出,比如两个很大的正整数相加,很可能会得到一个负数。但 Java 虚拟机规范中并没有对这种情况给出具体结果,因此程序是不会显式报错的。所以,大家在开发过程中,如果涉及到较大的数据进行加法、乘法运算的时候,一定要注意!
        当发生溢出时,将会使用有符号的无穷大 Infinity 来表示;如果某个操作结果没有明确的数学定义的话,将会使用 NaN 值来表示。而且所有使用 NaN 作为操作数的算术操作,结果都会返回 NaN。
    以下是算术指令点类型:
    • 加法指令:iadd、ladd、fadd、dadd
    • 减法指令:isub、lsub、fsub、dsub
    • 乘法指令:imul、lmul、fmul、dmul
    • 除法指令:idiv、ldiv、fdiv、ddiv
    • 求余指令:irem、lrem、frem、drem
    • 自增指令:iinc
  • 类型转换指令
    类型转换指令可以分为两种:
    • 1)宽化
      小类型向大类型转换,比如 :
      int–>long–>float–>double,对应的指令有:i2l、i2f、i2d、l2f、l2d、f2d。

      • 从 int 到 long,或者从 int 到 double,是不会有精度丢失的;
      • 从 int、long 到 float,或者 long 到 double 时,可能会发生精度丢失;
      • 从 byte、char 和 short 到 int 的宽化类型转换实际上是隐式发生的,这样可以减少字节码指令,毕竟字节码指令只有 256个,占一个字节。
    • 2)窄化
      大类型向小类型转换,比如:

      • 从 int 类型到 byte、short 或者 char,对应的指令有:i2b、i2s、i2c;
      • 从 long 到 int,对应的指令有:l2i;
      • 从 float 到 int 或者 long,对应的指令有:f2i、f2l;
      • 从 double 到 int、long 或者 float,对应的指令有:d2i、d2l、d2f。
  • 对象的访问与创建指令
    • 访问指令
      字段可以分为两类,一类是成员变量,一类是静态变量(也就是类变量),所以字段访问指令可以分为两类:
      访问静态变量:getstatic、putstatic。
      访问成员变量:getfield、putfield,需要创建对象后才能访问。

    • 创建指令

new #13 <java/lang/String>,创建一个 String 对象。
new #15 <java/io/File>,创建一个 File 对象。
newarray 10 (int),创建一个 int 类型的数组。
  • 方法调用与返回指令
    方法调用指令有 5 个,分别用于不同的场景:

    • invokevirtual:用于调用对象的成员方法,根据对象的实际类型进行分派,支持多态。
    • invokeinterface:用于调用接口方法,会在运行时搜索由特定对象实现的接口方法进行调用。
    • invokespecial:用于调用一些需要特殊处理的方法,包括构造方法、私有方法和父类方法。
    • invokestatic:用于调用静态方法。
    • invokedynamic:用于在运行时动态解析出调用点限定符所引用的方法,并执行。

方法返回指令根据不同类型返回:
在这里插入图片描述

  • 操作数栈指令
    常见的操作数栈管理指令有 pop、dup 和 swap。

    • 将一个或两个元素从栈顶弹出,并且直接废弃,比如 pop,pop2;
    • 复制栈顶的一个或两个数值并将其重新压入栈顶,比如 dup,dup2,dup×1,dup2×1,dup×2,dup2×2;
    • 将栈最顶端的两个槽中的数值交换位置,比如 swap。
  • 控制转移指令

    • 比较指令,比较栈顶的两个元素的大小,并将比较结果入栈。
    • 条件跳转指令,通常和比较指令一块使用,在条件跳转指令执行前,一般先用比较指令进行栈顶元素的比较,然后进行条件跳转。
    • 比较条件转指令,类似于比较指令和条件跳转指令的结合体,它将比较和跳转两个步骤合二为一。
    • 多条件分支跳转指令,专为 switch-case 语句设计的。
    • 无条件跳转指令,目前主要是 goto 指令。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Resean0223

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值