JVM-2.字节码和字节码指令

目录:

JVM-1.自动内存管理

JVM-2.字节码和字节码指令

JVM-3.类的加载机制

JVM-4.字节码执行和方法调用

JVM-5.程序编译与代码优化

JVM-6.Java线程内存模型和线程实现

我们都知道,java代码经过编译之后,会转换为字节码。那么

  • 字节码是什么?他的结构是怎么样的?
  • 字节码是怎么被执行的?
    这些问题本文都尝试去回答。

1.字节码是什么

字节码是什么,当我们编译java程序之后,便会生成.class文件,使用文本编辑器打开.class文件。
JVM-2.字节码和字节码指令_第1张图片
你会看到一大堆数字,不过你仔细观察会发现前8个字符居然是“cafe babe”,咖啡宝贝。这让人想起java的logo也是咖啡,而且java本身其实是一种咖啡的名字。事实上“cafe babe”是字节码文件的标识符,也被称为魔数,因为实际上他是一个16进制的数,而不是字符。在Linux系统中,识别一个文件并不是靠检验文件的后缀名,而是检验他的标识符。

另外值得注意的是,并不是一个.java文件编译成一个.class文件,而是一个类编译成一个.class文件。

那么除了开头的魔数,.class文件的结构是怎么样的呢?

class的文件结构

总体结构

class文件其实只有一种数据结构,就是无符号数,但是多个无符号数可以凑成不同的类型。根据无符号数的个数,分为U1,U2,U3,U4的等四种类型。另外由多个无符号数类型(指U1,U2,U3,U4)组成的数据结构称之为表。所以实际上整个class文件就是一个表。
JVM-2.字节码和字节码指令_第2张图片

  • 魔数和版本
    魔数在上面已经介绍过了。
    版本号标记的是字节码的版本,并不是当前文件的版本。低版本的执行器会拒绝执行高版本的字节码文件。
  • 常量池
    常量池用于存放字面量值,如int a=1;那么1就会被存放在常量池中。另外字符串池存放的位置是在堆中。
    此外,还会存放符号引用,所谓符号引用就是被引用的类的全称。符号引用会在类加载的时候被解析成实际引用类的地址。
  • 访问标记
    • 表示是类,接口,抽象类,枚举,注解
    • 是否为public
    • 是否为final
      多个结果使用&合并。
  • 当前类索引(this)
    当使用this时,指向的就是该属性记录的地址,指向当前类的地址。
  • 父类索引(super)
    指向父类的地址,使用super时指向的就是该地址。
  • 接口集合
    指向实现的接口的地址索引。因为接口可多实现,所以是集合。

字段表集合

描述类中声明的变量,以及实例变量,不包括方法内的局部变量。
字段表结构

名称 类型 说明
访问标记符 U2 public/private/protected/static/final/volatile/transient/enum
name_index U2 对常量池的引用,简单名称
descriptor_index U2 描述符,表示字段的类型,B:byte,C:char,D:double,F:float,I:int,J:long,S:short,Z:boolean,V:void,L:对象类型,如Ljava/lang/Object
attributes_count U2 属性的个数
attributes attribute_info 属性表,与class的属性表集合是一样的

方法表集合

名称 类型 说明
access_flags U2 访问标记符,比字段的访问标记符多出synchronized,native,strictfp(精确浮点数),abstract,少了volatile,transient
name_index U2 名称索引,指向方法具体实现
descriptor_index U2 先参数列表属性,再描述返回值属性
attributes_count u2 属性个数
attributes attributes 属性几个

方法集合里标记了方法的访问标记,返回值参数类型描述,但是方法的代码存再哪里?方法实际存储在属性表里的“Code”属性里。

属性表集合

可以看出,在很多结构里都使用到了属性表这一个属性,属性表没有顺序要求,只要不与已有的属性名重复即可。

名称 使用位置 说明
Code 方法表 存放具体的方法实现,实际是java代码编译的字节码指令
ConstantValue 字段表 存放final标记的常量
Deprecated 类,方法表,字段表 存放被标记遗弃的字段方法或类
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 仅当类为局部类或者匿名类时拥有的属性,用于标记该类所在的外围方法。

其他过多的属性不列出
JVM-2.字节码和字节码指令_第3张图片

2.字节码指令

字节码的执行

在上面的Code属性表中,实际存放代码的属性只有Code一个U1的属性。这令人有点迷惑,一个字节怎么存放代码呢?
实际上完整的指令由1个字节长度表示操作指令,以及紧跟其后的0个或n个参数构成。
java是面向操作栈的结构,大多数指令并不包括操作的数据。
如果不考虑异常,其执行代码的逻辑大致为以下的伪代码。

do{
    自动计算PC寄存器的值加1;
    根据PC寄存器指示的位置,从字节码流中取出操作指令;
    if(字节码存在参数) 
        从字节码流中取出参数;
    执行操作码所定义的操作;
}while(字节码流长度>0);

由于Code属性的类型是U1,所以实际上,操作码的总数不能操作256条。

字节码类型

字节码使用一个字母来表示类型

  • l:long
  • s: short
  • b: byte
  • c: char
  • f: float
  • d: double
  • a: reference,表示引用类型

九种类型的操作指令

类型 说明 例子
加载 用于将局部变量表和操作数栈之间的数据传输 iload(i表示int型,加载到操作数栈),istore(存储的局部变量),bipush,sipush等等
运算 计算数值 iadd,isub,imul,irem(取余),ior,iand,ixor,iinc(自增)等等
类型转换 转换类型 i2s(int转shrot),i2b(int转byte)等等
对象创建和访问 new(创建实例),newarray(创建数组)等等
操作数栈管理和访问指令 pop,dup,swap(交换栈顶的两个数)等等
控制转移指令 条件分支的跳转,如if语句 ifeq(相等),iflt(小于),goto等等
方法调用和返回指令 invokevirtual(调用实例方法),invokeinterface(接口方法),invokespecial(特殊方法,如初始化),invokestatic(静态方法),invokedynamic(动态方法)。ireturn(返回int)
异常处理指令 抛出异常,catch并不使用指令而是使用异常表查找,Code属性中的expection_info athrow
同步指令 当一个方法被标记为同步方法时,或者标记一段代码为同步代码块时,执行到同步代码时,线程会从方法表结构中的ACC_SYNCHRONIZED,标志是否被设置,如果被设置了,则必须等待,直到成功设置获得管程。执行代码后会释放管程。 monitorenter,monitorexit

你可能感兴趣的:(java)