深入理解java虚拟机系列第三版读后小记:(九)class结构与字节码

深入理解java虚拟机系列第三版读后小记:九 class结构与字节码

前言

本文将简单介绍一下的class的结构与字节码,了解将有利于我们熟悉jvm的对类文件的处理机制。

class类文件结构

众所周知,java是支持一个跨平台的语言,其支持跨平台的功能重要原因之一就是它有一个规范标准的class文件。
class文件是一个以8字节为基础的二进制流,各个项目严格紧凑顺序的排列在文件之中,也就是说没有空隙,没有分割符,整个class文件中全是运行必要数据,遇到占用8字节以上的空间数据,按照高位在前的分割方式分成多个8位字节进行存储。

class采用类c的伪结构存储存储数据,这种结构支持的数据类型有两种:无符号数和表

  • 无符号数属于基本数据类型,可以表示基本的类型信息如数字,utf-8的字符串,包括对象的索引信息。以u1,u2,u4,u8来代表其其空间是一个字节,二个字节,四个字节,八个字节。

  • 表是由多个无符号数字或者其他表作为数据结构组成的复合类型。通常命名方式以”_info“结尾。
    深入理解java虚拟机系列第三版读后小记:(九)class结构与字节码
    如图 class文件的格式。
    接下来按顺序解释下图中字段的含义

  • magic 魔数,用来表示这个class文件是否能被jvm识别的文件,之所以不用文件后缀名,是出于安全,文件后缀名可以随意篡改。不仅class文件采用魔数,诸多文件都采用魔数作为身份标识,如jpeg,gif等。class文件的魔数是0xCAFEBABE,直译为咖啡宝贝。

  • minor_version, major_version 即为jdk的次版本,主版本号

  • constant_pool_count 即为常量池计数器,类文件中的常量池数据是不固定的,所以需要个计数器。计数器是从1开始而不是和其他集合类型计数器从0开始,因为第0项作为保留项,目的在于指向常量池的索引某些情况下不需要引用常量池,所以索引的值就可以写0.

  • constant_pool常量池, class的资源仓库,与其他项目数据关联最多,也是占据了class文件最多的空间之一。常量池主要存放量大类常量

    • 字面量 比价接近java语言常量的概念,如字符串,final修饰的常量

    • 符号引用,编译原理方面的概念,主要包含以下几种常量

      • 被模块导出或者开方的包
      • 类和接口的全限定名
      • 字段名称和描述符
      • 方法名称和描述符
      • 方法句柄和方法类型
      • 动态调用点和动态常量
  • 访问标志 access_flags,用于识别类和接口的访问信息,如是否pulic, 是否abstract等。

  • this_class, super_class 分别为类索引和父类索引,维护类的继承关系

  • interface_count 接口索引的计数器

  • interface 接口索引集合

  • field_count 字段计数器

  • field_info 描述接口或类中声明的变量

  • method_count 方法计数器

  • method_info 方法集合

  • attribute_count 属性表的计数器

  • attribute_info 属性表的信息,此属性用来描述类的信息,如标识过期,异常表,签名,包括编译成代码等。

字节码

了解字节码指令集前,先讲一下jvm的指令:由一个字节长度的,代表某种特殊操作意义的数字(操作码),以及跟随其后零个或者多个代表此操作所需的参数(操作数)构成。由于jvm采用的是面向操作数栈而不是面向寄存器的架构,所以大多指令只有操作码,不含操作数,指令参数都存在操作数栈中。字节码就是一种jvm的指令集。

字节码指令类型

因为jvm只支持一个字节的指令,即字节码的指令集也就是0-255。所以几乎每个字节码都有对应的数据类型,如fload指令,就代表着从局部变量表中加载个float数据到操作数栈中。大多数与数据类型相关的指令的操作码,都有其助记符标明服务哪种类型,如i就是int,f就是float,a就是引用,l就是long等。
接下来将分别介绍一些操作的指令

存储和加载指令

将局部变量加载到操作数栈的指令: 助记符+load,如iload就是将证书加载到操作数,fload将单精度数据加载到操作数栈。

将一个数值从操作数栈存储到局部变量表的指令: 助记符+ strore,如istore

将一个常量加载到操作数栈: 助记符+const,如iconst等,还有其它的指令如bipush, sipuh, ldc.

运算指令

jvm运算只支持数据类型,其指令集就只有四种数字 类型,int,long,float,double。
运算操作有:

  • 加法指令,助记符+add,只支持四种数字类型,所以就四种指令,ladd,iadd,fadd,dadd
  • 减法指令:助记符+sub
  • 乘法指令: 助记符+mul
  • 除法指令:助记符+div
  • 求余指令:助记符+rem
  • 取反指令: 助记符+neg
    以及其他位操作指令

类型转换指令

就常见的int转long,float转double这些宽化类型转换无需字节码。
处理窄化类型转换就需要显示的使用字节码指令集,如i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2,窄化类型转换自然会存在精度缺失的问题。

其它指令

还有一些其他类型的指令

  • 对象创建指令,new,创建数组实例指令:newarray等
  • 操作数栈管理指令;出栈指令pop,入栈指令dup等
  • 控制转移指令,即条件语句指令,如ifeq等
  • 方法调用和方法返回指令,如invokeinterface调用接口指令
  • 异常返回指令:throw的异常指令athrow
  • 同步指令,支持synchronize的指令为monitorenter和monitorexi两条指令。

后话

之所以介绍类结构与字节码为了帮助读者可以更好的理解之后jvm对类的编译和对象实例化的过程。