美文网首页
类文件结构

类文件结构

作者: Zeppelin421 | 来源:发表于2022-03-31 18:40 被阅读0次

JVM与Java语言的关系?

    Java虚拟机不和包含Java在内的任何语言绑定,它只和“class文件”这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符合表以及若干其他辅助信息。


Java虚拟机提供的语言无关性

Class类文件结构

    Class文件是一组以8位字节为基础单元的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
    Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号(u1、u2、u4、u8)和表
无符号数 - 用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值
表 - 由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表习惯性以“_info”结尾


Class文件格式

魔数与Class文件的版本

每个Class文件头4个字节被称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。Class文件的魔数值为:0xCAFEBABE

第5和第6个字节是此版本号 (Minor Number),第7和第8个字节是主版本号 (Major Version)


Class文件版本号

常量池

    常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。

字面量比较接近接近Java语言层面的常量概念,比如文本字符串、声明为final的常量值等
符号引用则属于编译原理方面的概念,包括下面三类常量:

  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符

常量池中每一项常量都是一个表,共有14种表。这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位


常量池的项目类型
常量项的结构总表

访问标志

    用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类是否被声明为final等。


访问标志

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

    类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。


类索引查找全限定名的过程

字段表集合

    字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
包含的信息有:字段的作用域(public、private、protected)、是实例变量还是类变量(static)、可变性(final)、并发可见性(volatile)、可否被序列号(transient)、字段数据类型(基本类型、对象、数组)、字段名称。


字段表结构
字段访问标志

    name_index和descriptor_index它们是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。
全限定名:"com/zhaopin/zhiq/bootstrap;",仅仅把类名中的"."换成了"/",最后一般加";"
简单名称:指没有类型和参数修饰的方法或者字段名称,比如:inc()的简单名称就是inc
描述符:用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。对于数组类型,每一维度将使用一个前置的"["字符来描述。描述方法时,按照先参数列表,后返回值得顺序描述,参数列表按照参数的严格顺序放在一组小括号内。比如:方法

int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)

的描述符为 "([CII[CIII)I"


描述符标识字符含义

    字段表包含的固定数据项目到descriptor_index为止就结束了,不过在descriptor_index之后跟随着一个属性表集合用户存储一些额外的信息,字段都可以在属性表中描述零至多项的额外信息。
    字段表集合中不会列出从超类或者父类中继承而来的字段,但有可能列出原本Java代码中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。

方法表集合

    方法表的结构如同字段表一样,依次包括了访问标识符(access_flag)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项。方法里面的Java代码,经过编译器编译成字节码指令后,存放在方法属性集合中一个名为Code的属性里面。


方法表结构
方法访问标志

属性表集合

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


虚拟机规范预定义的属性

    对于每个属性,它的名称需要从常量池中引用,而属性值的结构则是完全自定义的。


属性表结构

1、Code属性


Code属性结构

max_stack代表了操作数栈(Operand Stacks)深度的最大值
max_locals代表了局部变量所需要的存储空间
code_length和code用来存储java源程序编译后生成的字节码指令(code_length理论上最大值可以达到2^32-1,但是虚拟机规范中明确限制了一个方法不允许超过65535条字节码指令。如果超长会导致编译失败。)
异常表包含4个字段:如果当字节码在第start_pc行到第end_pc行之间(不包含)出现了类型为catch_type或者其子类的异常,则转到第handler_pc行继续处理。当catch_type的值为0时,代表任意异常情况都需要转向到handler_pc处进行处理


异常属性表结构

2、Exceptions属性
    Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked Exception),也就是方法描述时在throws关键字后面列举的异常。


Exceptions属性结构

3、LineNumberTable属性
    LineNumberTable属性用于描述Java源码行号与字节码行号之间的对应关系。不是运行时必需属性,可在Javac中分别使用-g:none或-g:lines选项来取消或要求生成这项信息。


LineNumberTable属性结构

4、LocalVariableTable属性
    LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,不是运行时必需的属性,可在Javac中分别使用-g:none或-g:vars选项来取消或要求生成这项信息。


LocalVariableTable属性结构
local_variable_info项目结构

5、SourceFile属性
    SourceFile属性用于记录生成这个Class文件的源码文件名称。不是运行时必需的属性,可在Javac中分别使用-g:none或-g:source选项来关闭或要求生成这项信息。


SourceFile属性结构

6、ConstantValue属性
    ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)才可以使用这项属性。类似“int x=123”和“static int x=123”这样的变量定义在Java程序中是非常常见的事情,但虚拟机对这两种变量赋值的方式和时刻都有所不同。对于非static类型的变量(也就是实例变量)的赋值是在实例构造器<init>方法中进行的;而对于类变量,则有两种方式可以选择:在类构造器<clinit>方法中或者使用ConstantValue属性。目前Sun Javac编译器的选择是:如果同时使用final和static来修饰一个变量(按照习惯,这里称“常量”更贴切),并且这个变量的数据类型是基本类型或者java.lang.String的话,就生成ConstantValue属性来进行初始化,如果这个变量没有被final修饰,或者并非基本类型及字符串,则将会选择在<clinit>方法中进行初始化。


ConstantValue属性结构

7、InnerClasses属性
InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。


InnerClasses属性结构
inner_classes_info表的结构
inner_class_access_flag标志

8、Deprecated及Synthetic属性
Deprecated属性用于表示某个类、字段或者方法,已经不再推荐使用,它可以通过在代码中使用@deprecated注释进行设置。Synthetic属性代表此字段或者方法并不是由Java源码直接产生的,而是由编译器自行添加的,在JDK 1.5之后,标识一个类、字段或者方法是编译器自动产生的,也可以设置它们访问标志中的ACC_SYNTHETIC标志位,其中最典型的例子就是Bridge Method。所有由非用户代码产生的类、方法及字段都应当至少设置Synthetic属性和ACC_SYNTHETIC标志位中的一项,唯一的例外是实例构造器“<init>”方法和类构造器“<clinit>”方法。


Deprecated及Synthetic属性结构

9、StackMapTable属性
StackMapTable属性在JDK 1.6发布后增加到了Class文件规范中,它是一个复杂的变长属性,位于Code属性的属性表中。这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器(Type Checker),目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。


StackMapTable属性结构

10、Signature属性
Signature属性在JDK 1.5发布后增加到了Class文件规范之中,它是一个可选的定长属性,可以出现于类、属性表和方法表结构的属性表中。在JDK 1.5中大幅增强了Java语言的语法,在此之后,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。


Signature属性结构

11、BootstrapMethods属性
BootstrapMethods属性在JDK 1.7发布后增加到了Class文件规范之中,它是一个复杂的变长属性,位于类文件的属性表中。这个属性用于保存invokedynamic指令引用的引导方法限定符。


BootstrapMethods属性结构
bootstrap_method属性结构

字节码指令

Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。

1、字节码与数据类型
在Java虚拟机的指令集中大多数的指令都包含了其操作所对应的数据类型信息。i代表对int类型的数据操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。


Java虚拟机指令集所支持的数据类型

2、加载和存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,这类指令包括如下内容。

  • 将一个局部变量加载到操作栈:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
  • 将一个数值从操作数栈存储到局部变量表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
  • 将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
  • 扩充局部变量表的访问索引的指令:wide

3、运算指令
运算或算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。

  • 加法指令:iadd、ladd、fadd、dadd。
  • 减法指令:isub、lsub、fsub、dsub。
  • 乘法指令:imul、lmul、fmul、dmul。
  • 除法指令:idiv、ldiv、fdiv、ddiv。
  • 求余指令:irem、lrem、frem、drem。
  • 取反指令:ineg、lneg、fneg、dneg。
  • 位移指令:ishl、ishr、iushr、lshl、lshr、lushr。
  • 按位或指令:ior、lor。
  • 按位与指令:iand、land。
  • 按位异或指令:ixor、lxor。
  • 局部变量自增指令:iinc。
  • 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp。

4、类型转换指令
类型转换指令可以将两种不同的数值类型进行相互转换,这些转换操作一般用于实现用户代码中的显式类型转换操作。指令包括i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。

5、对象创建与访问指令
Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。

  • 创建类实例的指令:new。
  • 创建数组的指令:newarray、anewarray、multianewarray。
  • 访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的指令:getfield、putfield、getstatic、putstatic。
  • 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload。
  • 将一个操作数栈的值存储到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore。
  • 取数组长度的指令:arraylength。
  • 检查类实例类型的指令:instanceof、checkcast。

6、操作数栈管理指令
如同操作一个普通数据结构中的堆栈那样,Java虚拟机提供了一些用于直接操作操作数栈的指令

[插图]将操作数栈的栈顶一个或两个元素出栈:pop、pop2。[插图]复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2。[插图]将栈最顶端的两个数值互换:swap。

7、控制转移指令
控制转移指令可以让Java虚拟机有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序

  • 条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。
  • 复合条件分支:tableswitch、lookupswitch。
  • 无条件分支:goto、goto_w、jsr、jsr_w、ret。

8、方法调用和返回指令
方法调用的指令。

  • invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。
  • invokeinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
  • invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
  • invokestatic指令用于调用类方法(static方法)。
  • invokedynamic指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面4条调用指令的分派逻辑都固化在Java虚拟机内部,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
    方法返回指令是根据返回值的类型区分的,包括ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用。

9、异常处理指令
在Java程序中显式抛出异常的操作(throw语句)都由athrow指令来实现

10、同步指令
Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。
Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义

相关文章

  • 类文件结构【Class类文件的结构】

    概述 任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来,类或接口并不一定都得定义在文件里(如类或...

  • 类文件结构

    本文大体讲一下结构,着重从理解层面看,就是当时我最难理解的地方讲,并不会深入的讲解某一块的具体结构信息,如果要看结...

  • 类文件结构

    class文件结构 Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Cla...

  • 类文件结构

    类的二进制文件中的主要结构 无符号数和表 u1,u2,u4,u8。表由无符号数和表构成 魔数 1-4字节:确定文件...

  • 类文件结构

    概述 Class文件是一组以8位字节为基础单位的二进制流,各个数据项目按照顺序紧地排列在CLass文件之中,中间没...

  • 类文件结构

    一、概述 在计算机商业领域中,不同的硬件体系结构和不同的操作系统长期并存是必然的结果,所以Java 提出了“Wri...

  • 类文件结构

    语言无关性 JAVA虚拟机与Class文件关联,与具体的语言没关系。任何语言只要能编译成Cla...

  • 类文件结构

    语言无关性:jvm和字节码格式 jvm不和java在内的任何语言绑定,他只和“class”这种特定的二进制格式文件...

  • 类文件结构

    摘抄: 陈树义 Java技术精选 -各个语言的编译器吧文件编译成字节码,jvm把字节码解释给操作系统,或者是编译成...

  • 类文件结构

    前言 本文是《深入理解Java虚拟机》第6章的部分知识点,这一章正如作者所说,对数据结构的讲解确实枯燥,对于失眠治...

网友评论

      本文标题:类文件结构

      本文链接:https://www.haomeiwen.com/subject/jzynoctx.html