本文继续使用上次的Test.class文件,它是由下面单独的一个类文件编译而成的,没有包。

6. 索引(Index)

索引又分类索引、父类索引和接口索引集合,类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件依靠这些索引数据来确定这个类的继承关系。所有类(除了java.lang.Object)都只有一个父类索引(Java的单继承),即父类索引不为0,只有java.lang.Object的父类索引为0。接口索引用来描述该类实现了哪些接口,它们的出现顺序是按照implements语句后接口的先后顺序出现的,如果这个类是一个接口就按照extends后面出现的顺序来。

类索引和父类索引各自指向一个CONSTANT_Class_info的类描述符常量,然后通过CONSTANT_Class_info可以定位到一个CONSTANT_Utf8_info类型的常量中的全限名字符串。而接口索引集合则以接口计数器开头,和前面常量池类似,若计数器表示n则后面紧跟着的n个u2数据是表示该类实现的n个接口的类索引,分别指向对应的类描述符常量。

全限名:"java/lang/Object"表示Object类的全限名,将类全名中的“.”替换成“/”而已,多个全限名之间是“;”分隔。

仍然以我上次的那个Test.class文件为例,这里三个u2类型的值分别为Ox0005、Ox0006、Ox0000,前两个分别表示的是类索引、父类索引所指向的常量描述符。第三个表示接口集合的个数,这里为0即没有实现任何接口。假设为2,则表示接下来的2个u2数据表示实现的两个接口,每个u2数据也指向的是常量描述符。

7.字段表集合(Field Info)

字段表(field_info)用于描述接口或者类中声明的变量。字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。字段包含的信息比较多,包含以下内容:

  • 字段的作用域:public、private、protect修饰符
  • 变量类型(类变量or实例变量):static
  • 可变性:final
  • 并发可见性:volatile
  • 可否序列化:transient
  • 数据类型:基本数据类型、对象、数组
  • 字段名称

上面的这些信息除了字段数据类型和字段名称其他都是以布尔值来描述的,有就是true且对应一个标志位,没有则false,这种表示方法和上一节的Access Flags一样。字段数据类型和字段名称是引用的常量池中的常量来描述,可能是CONSTANT_Class_info也可能是CONSTANT_Utf8_info。

根据Java语言的语法我们可以知道,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三个标志只能选一个,ACC_FINAL、ACC_VOLATILE不能同时存在,接口必须有ACC_PUBLIC、ACC_STATIC、ACC_FINAL标志。

描述符

描述符的作用是用来描述字段的数据类型、方法的参数列表(数量、类型、顺序)和返回值。其中基本数据类型以及void返回值类型都是用一个大写字母来表示的,对象的类型由一个L加对象全限名表示。

基本数据类型和普通类型都已经知道怎么表示了,但Java中有一个特殊类型就是数组类型,它是在编译期产生的,它的描述符是在变量描述符前面加一个"[",如果是二维则加两个[,比如"[["。例如一个String[][]记录为[[Ljava/lang/String,一个int[]记录为[I

如果是描述一个方法则在描述符前面加一个括号“()”,如果有参数则在其中按顺序添加描述符即可。例如一个String toString(char[] c,int a,String[] b)的描述符为:“([CI[Ljava.lang.String)Ljava.lang.String”。

这里同样以Test.class文件来验证,第一个u2数据是容量技术器fields_count,这里是Ox0000,说明没有字段表数据,看文章开头的java代码,确实没有定义任何字段。由于在编译class文件开始没有考虑周全,没有定义字段,这里容量技术器为0也就看不到后面的字段描述内容,这里先假设是Ox0001,即有一个字段。第二个u2数据是访问标识符access_flags,假设这里是Ox0002,说明字段标志为ACC_PRIVATE。第三个u2数据是字段名称name_index,假设值为Ox0005,指向#5的常量池CONSTANT_Utf8_info字符串。第四个u2数据是字段描述符,这里是Ox0007,指向#7的常量池字符串。

8. 方法表集合

方法表的描述和字段表集合描述形式一样,只需要按照对应的表格对照就可以了。方法表结构依次包含了access_flags(访问标志)、name_index(方法名索引)、descriptor_index(描述符索引)、attribute(属性表集合)几项。方法内的具体代码存放在属性表集合attribute的名为“Code”的属性里面。

方法表结构表:

方法访问标志表:

继续以Test.class文件分析,容量计数器methods_count的值为Ox0002,表示由两个方法,疑惑?看文章开头的代码只有一个main方法啊,为什么会有两个?其实字节码中包含了平时省略了的无参构造方法<init>。

紧跟着的是2个方法描述集合,这里以第一个无参构造来解释,首先是访问标志access_flags,值是Ox0001,查表可知是ACC_PUBLIC类型的,然后是方法名索引name_index,值是Ox0007,指向的是常量池CONSTANT_Utf8_info字符串,即#7,我们查看反编译的代码可以看到#7确实是<init>。

然后是描述符索引descriptor_index,值是Ox0008指向的是常量项#8,反编译后看到是()V,构造方法无返回值,所以用的void的标识字符V,但是在书写代码时不能显式加void,因为其验证是在编译期。紧接着的是属性表集合的属性计数量attributes_count,这里是Ox0001,说明只有一个属性,即前面说的“Code”属性。

接下来的就是分别表示每一个属性的具体指向,这里只有一个当然就只需看一个u2数据,这里是Ox0009,指向的是常量项#9,反编译结果#9确实是Code。

如果方法在子类中没有被重写,方法表集合中就不会出现来自父类的信息。

从方法表集合可以看出,Class文件对一个方法的特征识别(《Java虚拟机规范》称之为特征签名)有很多,比如方法描述符、访问控制标志、返回值、属性表等。

这里我想起来了之前腾讯一个面试官问我的问题“重载的验证是在哪个阶段?”,当时我没回答好这个问题,看了《深入理解Java虚拟机》这一节的内容才知道,对于Java方法的重载是在编译器验证的,在Java语义里规定:只要方法名、参数内容及顺序相同则视为非法重载,而对返回值、修饰符等没有严格要求。而在Class文件里对一个方法的特征签名比编译期的多,也就是说如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法存在于同一个Class文件的。

9.属性表集合

属性表(attribute_info)存在于Class文件、字段表、方法表等,它用于描述某些场合专有的信息。在class文件中对属性表的限定并不是很严格,只要不要与已有属性名重复,任何不人实现的编译器都可以向属性表中写入自己定义的属性信息,虚拟机在运行时会忽略掉它不认识的属性。这一部分内容较多并且不固定,建议读者阅读最新的《Java虚拟机规范》或《深入理解Java虚拟机——周志明 著》。

本文是笔者阅读《深入理解Java虚拟机》一书时的简单总结和实践。参考文献:《Java虚拟机规范(第二版)》、《深入理解Java虚拟机》

END

详解Class类文件的结构(下)相关推荐

  1. 详解Class类文件的结构(上)

    前言 相信搞Java开发的同学都经常会接触到Class类文件,了解了JVM虚拟机之后也会大量接触到class字节码,那么它到底是什么样的文件?内部由什么构成?虚拟机又是如何去识别它的?这篇文章就来学习 ...

  2. Linux中/proc目录下文件详解 /proc/devices文件 /proc/modules文件

    http://blog.chinaunix.net/uid-10449864-id-2956854.html 原来对linux系统中的/proc目录不是很了解,只知道可以查看cpu,内存等相关的信息, ...

  3. 技术揭秘之详解回收站删除文件恢复

    |=------------------------------------------------------------------------=| |=--------------=[技术揭秘之 ...

  4. python怎么导入文件-Python文件如何引入?详解引入Python文件步骤

    python基本语法--引入Python文件 1.新建python文件 :在同目录lib下创建mylib.py和loadlib.py两个文件 2.在mylib.py文件中创建一个Hello的类 并且给 ...

  5. 【无标题】类模板详解\n类模板的定义及实例化\ntemplate\u003Cclass 模板参数>\nclass 类名 {\n // 类定义\n};\n\ntemplate\u003Ctypen

    类模板详解\n类模板的定义及实例化\ntemplate\u003Cclass 模板参数>\nclass 类名 {\n    // 类定义\n};\n\ntemplate\u003Ctypenam ...

  6. 类模板详解\n类模板的定义及实例化 模板参数

    类模板详解\n类模板的定义及实例化\ntemplate\u003Cclass 模板参数>\nclass 类名 {\n    // 类定义\n};\n\ntemplate\u003Ctypenam ...

  7. FreeMarker基本语法详解及模板文件的组成(二)

    海浪上次给大家分享了FreeMarker基本语法详解及模板文件的组成(一)海浪今天继续分享FreeMarker基本语法详解及模板文件的组成(二) 3.2 输出变量值<?xml:namespace ...

  8. linux rm命令参数及用法详解---linux删除文件或目录命令

    http://www.linuxso.com/command/rm.html linux下rm命令使用详解---linux删除文件或目录命令 用户可以用rm命令删除不需要的文件.该命令的功能为删除一个 ...

  9. Class类文件的结构

    2019独角兽企业重金招聘Python工程师标准>>> Class类文件的结构 Class类文件的结构 任何一个Class文件都对应着唯一一个类或接口的定义信息,但反之类和接口并不一 ...

最新文章

  1. write/read/send/receive函数比较
  2. 微博客之后有可能是“切客”
  3. Android常用类库包介绍
  4. mysql数据库集群备份策略_mysql高可用方案之集群(cluster)
  5. python文本分类算法_Python-基于向量机SVM的文本分类
  6. c++语言自定义操作符,C++11新特性之自定义字面量
  7. MVC和WebApi中设置Area中的页为首页
  8. android job service,服务保活那些事(Job Service ,JobScheduler)
  9. 文件系统中,Path和Directory的区别
  10. js传参中文格式不对乱码
  11. Sql Server常用命令整理篇:生成连续日期序列并循环
  12. PS学习-锐化和修饰照片(一)--高反差锐化并提升立体感
  13. Robcup2D足球学习记录【2020.01.18】
  14. 美团·北极星开发对接避坑指北(Java)
  15. vue ajax传输数组,ajax请求回数组数据,Vue页面数组没同步问题
  16. Python基础语法笔记
  17. H3C配置远程登录(console、telnet、ssh)
  18. Go语言实现枚举方法,const和iota结合轻松实现
  19. 蓝桥杯基础练习之十六进制转八进制
  20. net framework 4 0如何安装 安装失败怎么办

热门文章

  1. SpringBoot使用Jsp
  2. Java学习之SpringBoot整合SSM Demo
  3. FineReport实现java报表统计图表的效果图
  4. 【BZOJ-2937】建造酿酒厂 前缀和 + 展环为链 + 乱搞
  5. ThreadPoolExecutor源码学习(2)-- 在thrift中的应用
  6. 解决Android中的ERROR: the user data image is used by another emulator. aborting的方法
  7. 通过JavaScript操作HTML中select标签
  8. python变量标识符_python中的变量和标识符
  9. 组个最小数C语言pta,PTA|C语言:组个最小数
  10. 连接mysql报错有乱码_连接mysql服务器报错时,出现乱码