[深度理解java虚拟机]第六章 类文件结构

6.1 概述

6.2 无关的基石

  • 介绍java虚拟机可以运行其他语言编译得到的字节码文件。

6.3 Class类文件结构

  • class文件中只有两种数据类型:无符号数(属于基本数据类型)和表(由多个符号数或者其他表作为数据项的复杂数据类型。
  • 无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时称这一系列连续的某一类型的数据为某一类型的集合。

6.3.1 魔数与Class文件的版本

  • 每个Class文件的头4个字节成为魔数(Magic Number),它的作用是唯一确定这个文件是否为一个能被虚拟机接受的Class文件。
  • 使用魔数来进行识别主要是基于安全的考虑,因为文件扩展名可以随意改动。
  • Class文件的魔数值为:0xCAFEBABA(“咖啡宝贝”)
  • 紧接着4个字节是次版本号和主版本号

6.3.2 常量池

  • 可以理解为Class文件之中的资源仓库。常量池的入口需要放置一个u2(代表2个字节)类型的数据,代表常量池容量计数器的值。
  • 常量池的项目类型
    在这里插入图片描述
  • 14种常量项结构总表
    在这里插入图片描述在这里插入图片描述
  • 分析Class文件字节码工具:javap,指定参数-verbose输出文件字节码内容。

6.3.3 访问标志

  • 紧接着的两个字节代表访问标志,用于识别一些类或者接口层次的访问信息。
  • 标志位以及标志的含义
    在这里插入图片描述

6.3.4 类索引、父类索引与接口索引集合

  • 类索引和父类索引都是一个u2类型的数据,而接口索引是一组u2类型的数据集合,由这三项数据来确定类的继承关系。

6.3.5 字段表集合

  • 字段表用于描述接口或者类中声明的变量
  • 字段表结构
    在这里插入图片描述
  • 字段访问标志
    在这里插入图片描述

6.3.6 方法表集合

  • 对方法的描述与对字段的描述几乎采用了完全一致的方式,方法表和字段表一样。
  • 访问标志表有所差异。因为volatile和transient关键字不能修饰方法,还有synchronized、native等可以修饰方法。

6.3.7 属性表集合

用于描述某些场景专有的信息,在class文件、字段表、方法表都可以携带自己的属性表集合。

  1. Code属性:java程序方法体中的代码经过javac编译器处理后,最终变成字节码指令存储在Code属性内。
  2. Exceptions 属性:是列举出方法中可能抛出的受查异常,也就是方法中描述时在throws关键字后面列举的异常。
  3. LineNumberTable属性:用于描述java源码行号与字节码行号之间的对应关系。并不是运行时必需的属性。
  4. LocalVariableTable属性:用于描述栈帧中局部变量表中的变量与java源码中定义的变量之间的关系。也不是运行时必需的属性。
  5. SourceFile属性:用于记录生成这个Class文件的源码文件名称。也是可选的。
  6. ConstanValue属性:作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量才能使用这项属性。
  7. InnerClasses属性:用于记录内部类与宿主类之间的关联。

6.4 字节码指令简介

  • java虚拟机指令由一个字节长度的(因此指令集的操作码总数不超过256条),代表着某种特定含义的数字(称为操作码)以及跟随其后的零至多个代表此操作所需要参数(操作数)构成。
  • 由于java虚拟机采用面向操作数栈而不是寄存器的架构,所以大多数的指令都不包含操作数,只有一个操作码。

6.4.1 字节码与数据类型

  • 在指令集中大多数指令都包含了其操作所对应的数据类型信息。如iload指令用于从局部变量表中加载int型的数据到操作数栈中,fload等。

6.4.2 加载和存储指令

  • 用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
  • 将一个局部变量加载到操作数栈中:iload、iload_(n表示操作局部变量表中的第n个变量)、lload、lload_、fload、fload_、dload、dload_、aload(加载引用类型)、aload_。
  • 将一个数数值从操作数栈中存储到局部变量表:istore、istore_、dsotre、dstore_,asotre、astore_。
  • 将一个常量加载到操作数栈:iconst_、lconst_等

6.4.3 运算指令

  • 用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入操作栈顶。
  • 运算指令大致分两种对整型数据运算的指令和对浮点数运算的指令,由于java虚拟机中没有支持byte、short、char、boolean类型的算法指令,这类数据都应使用int类型的指令替代。
  • iadd、isub、imul、idiv、irem(取余)、ineg(取反)等。

6.4.4 类型转换指令

  • 用于将两种不同的数值类型进行相互转换。
  • l2i(将long转换为int)、f2i(将float转换为int)。

6.4.5 对象创建与访问指令

  • 创建类实例的指令:new
  • 创建数组的指令:newarray、anewarray、myltianewarray。
  • 访问类字段(static字段)和实例字段的指令:getfield、putfield、getstatic、putstatic。
  • 将一个数组元素加载到操作数栈的指令:iaload(i表示int,a表示array)、laload。
  • 将一个操作数栈的值存储到数组元素中的指令:iastore、fastore。
  • 去数组长度的指令:arraylength。
  • 检查类实例类型的指令:instanceof、checkcast。

6.4.6 操作数栈管理指令

-用于直接操作操作数栈的指令。

  • 将操作数栈的栈顶一个或两个元素出栈:pop、pop2。
  • 复制栈顶一个或两个数值并将复制值重新压入栈顶:dup、dup2。
  • 将栈最顶端的两个数值进行交换:swap。

6.4.7 控制转移指令

  • 可以让java虚拟机有条件或无条件地从指定的位置指令执行。
  • 条件分支:ifeq、iflt、ifgt、ifnull、if_icmpeq、if_icmpne等。
  • 复合条件分支:tableswitch、lookupswitch。
  • 无条件分支:goto、

6.4.8 方法调用和返回指令

  • invokevirtual指令用于调用对象的实例方法。
  • invokeinterface指令用于调用接口方法。
  • invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类帆帆发。
  • invokestatic指令用于调用类方法。
  • invokedynamic用于在运行时动态解析出调用点限定符所引用的方法。

6.4.9 异常处理指令

  • java程序中显示抛出异常的操作(throw语句)都athrow指令来实现。

6.4.10 同步指令

  • java虚拟机支持方法级的同步和方法内部一段指令的序列同步,都是使用管程来支持的。
  • 方法级的同步是隐式的无须通过字节码指令来控制,是通过方法常量池的方法表结构中的ACC_SYNCHARONIZED访问标志得治一个方法是否声明为同步方法。
  • 同步一段指令集序列是由java语言中的synchronized语句块来表示。java虚拟机的指令集中有moniorenter和monitorexit两条指令来支持synchronized关键字的语义。
发布了84 篇原创文章 · 获赞 50 · 访问量 7011

猜你喜欢

转载自blog.csdn.net/qq_43115606/article/details/104083474