Java虚拟机--Class文件结构

Class文件是一组以8位字节为基础单位的二进制字节流,各个数据项目严格按照顺序排列在Class文件中,中间没有任何分隔符

如果一个数据需要8位以上的空间,则会按照高位在前(最高为字节在地址最低位,最低位字节在地址最高位)的方式分割成若干个8位字节进行存储。

Class文件只有两种数据类型:无符号数和表。

  • 无符号数:属于基本数据类型,以u1, u2, u4, u8来分别代表一个字节、两个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照utf-8编码构成的字符串值。
  • :表是由多个无符号数或其他表作为数据项的复合数据类型,所有表都习惯性的以“_info"结尾。表用于描述有层次关系的复合结构数据,整个class文件本质上就是一张表。

魔数与Class文件版本:

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

紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号,第7和第8字节是主版本号。

常量池:

紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一。

由于常量池中常量的数量不固定,所以在常量池的入口需要放置一项u2类型的数据,代表常量容器计数值。与Java语言习惯不同,它是以计数值1开始的。

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

字面量:接近Java语言层面的常量的概念,如文本字符串、声明为final的常量值等。

符号引用:属于编译原理方面的概念,包括三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

常量池中每一项常量都是一个表,表开始的第一位是一个u1类型的标置位,代表当前这个常量属于哪种常量类型。

访问标志:

常量池结束后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或接口的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。

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

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

类索引用于确定这个类的全限定名,父索引用于确定这个类的父类的全限定名。接口索引集合用来描述这个类实现的接口,这些被实现的接口按照implements语句(如果本身是接口,则应该是extends语句)的顺序排列。

字段表集合:

字段表用于描述接口或类中声明的变量。字段包括类级变量和实例级变量,但不包括方法内部声明的局部变量。

一个字段需要保存哪些信息?字段的作用域(public、protected、private)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile)、可否被序列化(transient)、字段的数据类型(基本类型、对象、数组)、字段名称。

字段表结构依次包括:访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor)、属性表集合(attribute_info)。除了属性表集合是表,其他类型都是一个u2类型。

  • 访问标志(字段修饰符):其中存储的是一个字段访问标志,比如字段如果为public,则存储ACC_PUBLIC。
  • 名称索引:存储的是常量池引用,代表着字段的简单名称。
  • 描述符索引:存储字段和方法的描述符,通过描述符表示字符来定义,如:java.String toString()方法的描述符为:“()Ljava/lang/String”。
  • 属性表集合:字段表后可以跟一个属性表集合用于存储一些额外信息,字段可以在属性表中描述零至多项额外信息。

方法表集合:

方法表结构同字段表一样,依次包括了:访问标志、名称索引、描述符索引、属性表集合这几项。除了属性表集合是表,其他类型都是一个u2类型。

方法表中没有保存方法体,方法体存放在方法属性表中的“Code”属性里面。属性表是Class文件最具扩展性的一种数据项目。

属性表集合:

Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景的专有信息。《Java虚拟机规范(Java SE7)》中预定义了21项属性。

1、Code属性

Java方法体中的代码经过javac编译器处理后,最终变为字节码指令存储在Code属性内。

2、Exceptions属性

列举出方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常。

3、LineNumberTable属性

用于描述Java源码行号和字节码行号(字节码偏移量)之间的对应关系。该属性不是必须的,如果选择不生成该属性,对程序运行时最主要的影响是抛出异常时不会显示出错行号,也无法按照源码行设置断点。

4、LocalVariable属性

用于描述栈帧中局部变量表中的变量与Java源码中定义变量之间的关系,它不是必须的属性。如果选择不生成该属性,在调试期间无法根据参数名称从上下文获得参数值。

5、SourceFile属性

用于记录生成这个class文件的源文件名称。该属性也是可选的。如果不生成该属性,当抛出异常时堆栈中不会显示出错代码所属的文件名。

6、ConstantValue属性

通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量才可以使用这项属性。

变量赋值问题: 对于非static的变量(即实例变量)的赋值实在实例构造器<init>方法中进行的; 对于static变量(类变量),有两种方式选择:在类构造器<clinit>方法中或者使用ConstantValue属性。目前Sun Javac编译器的选择是:如果同时使用final和static来修饰一个变量(这里称为常量更贴切),并且这个变量的类型是基本数据类型或String时,就生成ConstantValue属性来初始化;如果这个变量没有被final修饰,或者并非基本类型及字符串,则在<clinit>方法中进行初始化。

7、InnerClasses属性

用于记录内部类和宿主类之间的关联。如果一个类中定义了内部类,那编译器就会为它和它的内部类生成InnerClasses属性。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python

举例详解Python中的split()函数的使用方法

这篇文章主要介绍了举例详解Python中的split()函数的使用方法,split()函数的使用是Python学习当中的基础知识,通常用于将字符串切片并转换为列...

2145
来自专栏前端架构与工程

JavaScript中的原型链原理

  工作中经常解除到prototype的概念,一开始错误的认为prototype是对象的原型链,其实prototype只能算是JavaScript开放出来的原型...

1896
来自专栏LEo的网络日志

python技巧分享(七)

3368
来自专栏个人随笔

论 异常处理机制中的return关键字

Java中,执行try-catch-finally语句需要注意: 第一:return语句并不是函数的最终出口,如果有finally语句,这在return之后还会...

2868
来自专栏mathor

Java自增自减运算符神坑笔试题

1353
来自专栏PHP在线

php

单例设计模式 1.控制一个类只能创建一个对象,设置构造函数为私有的。 2.设置静态方法调用类中方法返回实例化。 3.在类中设置静态属性存放实例化对象。 ? 命名...

3297
来自专栏jojo的技术小屋

原 四、变量、作用域和内存问题

作者:汪娇娇 时间:2017年11月5日 一、基本类型和引用类型的值 基本类型指的是简单的数据段,引用类型指那些可能由多个值构成的对象。 基本类型的值保存在变量...

2808
来自专栏Python中文社区

7个提升Python程序性能的好习惯

使用局部变量替换模块名字空间中的变量,例如 ls = os.linesep。一方面可以提高程序性能,局部变量查找速度更快;另一方面可用简短标识符替代冗长的模块变...

1516
来自专栏十月梦想

ES6基础语法之set

1234
来自专栏zhangdd.com

nginx location匹配规则

~      #波浪线表示执行一个正则匹配,区分大小写 ~*    #表示执行一个正则匹配,不区分大小写 ^~    #^~表示普通字符匹配,如果该选项匹配...

1024

扫码关注云+社区

领取腾讯云代金券