类文件结构

在说完了JVM内部结构后,接下来我们需要说一下另外一个非常重要的基础概念Class类结构。

我们既然知道了开发的Java源代码会首先被编译成字节码文件保存,JVM的类加载器会读取这些文件内容,然后将其转换为Class类对象保存到JVM管理的运行数据区里。那么这个编译后的Class类文件的结构如何呢?我们今天来简单说一下。

其实JVM的规范里有详细的定义和说明,不过它涉及了很多专业的名称和术语,还有就是说的比较简洁,理解起来还是有些难度。

类文件结构

简单说来从存储字节码的.class文件中读取的内容由10个基本组成部分构成:

简略结构

  • magic: 0xCAFEBABE
  • minor_version和major_version: 包括类文件的主次版本号
  • constant_pool: 当前类的常量池
  • access_flags: 当前类访问标志
  • this_class: 当前类名字
  • super_class: 其父类名字
  • interfaces: 当前类实现的任何接口
  • fields: 当前类定义的任何字段
  • methods: 当前类包含的方法
  • attributes: 类的任何属性(例如源文件的名称等)

一般情况下,该文件是由8位字节基本单位长度字节流构成,对于那些16位,32位,甚至64位的它们将分别读取2个,4个和8个基本单位来表示。

这类多字节数据项都将是以大端顺序存储的,也就是说高位字节在前。

我们在Java开发时,常用的读写这类数据的接口是java.io.DataInput和java.io.DataOutput,对应的类有java.io.DataInputStream和java.io.DataOutputStream。

类文件结构

截图中的u1,u2,u4 它们分别表示无符号的1字节,2字节和4字节类型,它们分别对应着我们Java的基础数据类型中的byte,short,int等。我们完全可以使用java.io.DataInput接口定义的readUnsignByte,readUnsignShort, readUnsignInt等读取其值。

另外一类就是长度在加载之前是未知的,有可变长度的部分,如常量池,方法,属性等。这些部分的组织方式是以它们的大小或长度作为前缀的。

通过这种方式,JVM在实际加载可变长度区段之前就知道它们的大小。

在Java ClassFile中不同部分的顺序是严格定义的,这样JVM就知道每一步应该要加载什么,以及加载不同组件的顺序。

关于魔幻数字的故事

詹姆斯·高斯林(James Gosling)曾经解释过这个神奇数字的历史:

他说“我们过去常去一个叫 St Michael’s Alley的地方吃午饭。据当地传说,在黑暗的过去,Grateful Dead乐队在成名之前经常在那里演出。这是一个非常时髦的地方,绝对是一个‘Grateful Dead Kinda Place’。Jerry死后,他们甚至建了一座佛教风格的小神龛。当我们过去常去那里,我们把那个地方称为‘Cafe Dead’。

沿着这思路,有人注意到这是一个十六进制数。当时我重新整理了一些文件格式代码,需要几个神奇的数字,一个用于持久对象文件,一个用于类文件。所以我就选用CAFEDEAD作为对象文件格式,并在查询中添加了4个字符的十六进制单词,它们与“CAFE”匹配。我意外获得了BABE,所以就决定使用CAFEBABE。在那个时候,它似乎并不是那么重要,或者注定要去任何地方,除了历史的垃圾桶。

因此CAFEBABE成为类文件格式,而CAFEDEAD则是持久对象格式。但持久性对象设施消失了,随之消失的还有CAFEDEAD的使用——它最终被RMI取代。”

类文件版本

类文件的下4个字节包含主版本号和次版本号。我们的JVM会通过这组数字验证和标识类文件。

如果这个数量大于JVM可以装载的数量,那么类文件将被错误拒绝,并提示java.lang.UnsupportedClassVersionError。

我们通常可以使用javap命令行来查看任何Java类文件的类版本。

先定义一个MyClass.java文件,然后编译成MyClass.class

public class Main {public static void main(String [] args) {int my_integer = 0xFEEDED;}}

我们使用javap命令查看:

$ javap -verbose MyClass

在上面的主类上执行javap,我们得到如下的符号表。

javap 显示类文件内容

关于常量池

我们知道所有的类或者接口的定义在应用程序运行过程中都不会发生变化的,因此所有与类或接口相关的常量都将存储在常量池中。

它们包括类名、变量名、接口名、方法名和签名、最终变量值、字符串字面量等。

这些常量在常量池中被存储为可变长度数组元素。

这些常量可变数组的前面是它的数组大小,因此JVM知道在加载类文件时需要多少常量。

而在每个数组元素中,第一个字节表示一个标记位,该标记指定数组中该位置的常量类型,JVM通过读取这个字节的标记来标识常量的类型。

因此,如果这个字节标记表示一个字符串文字,那么JVM知道接下来的两个字节表示字符串文字的长度,而条目的其余部分是字符串文字本身。

我们同样可以使用javap命令分析任何类文件的常量池。

常量池内容

上图中常量池总共有15个条目。

#1是方法public static void main;

#2用于整数值 0xFEEDED(16707053)

#3和#4分别是我们类的this和super类。

剩余其它都是存储字符串文本的符号表。

关于访问标识

它是一个2个字节的条目,用类指示文件是定义的类还是接口,如果是一个类,它是公共的、抽象的还是final的。

这些访问标志包括ACC_PUBLIC,ACC_FINAL,ACC_SUPER,ACC_INTERFACE,ACC_ABSTRACT,ACC_SYNTHETIC,ACC_ANNOTATION,ACC_ENUM等等。

关于this Class

this Class类是一个2个字节的条目,其内容是指向常量池中的索引。

图片来自网络

比如在上面的图中,this类的值0x0007是常量池中的索引。

this 所指向的对应项Constant_pool[this_class]在常量池中有两部分,第一部分是一个字节的标记类表示在常量池中的条目类型。

这种情况下是类或者接口类型。在上面的图表中,这是用橙色表示的。第二个条目部分是两个字节,同样在常量池中有索引。

在上面的图中,两个字节包含值0x0004。因此它指向Constant_pool[0x0004],它的值是具有接口或类名称的字符串文字。

关于super Class和其它集合组件

排在this Class后面那个字节就是Super Class,跟this Class类似,它是2个字节的值,也是一个指向常量池的指针,该常量池具有类的超类的条目。

接口集合

包含由该文件中定义的类或接口实现的所有接口。

接口部分开始的两个字节是提供有关正在实现的接口总数的信息的计数,紧接着是一个数组,该数组包含了被该类实现的接口在常量池里的索引。

字段集合

字段是类或接口的实例或类级变量或属性存储的地方。

字段部分只包含由文件的类或接口定义的字段,而不包含从超类或超接口继承的字段。

它的前两个字节表示计数,是该部分中包含的所有字段数,跟在其后的是每个字段的可变长度数组。数组中每个元素都表示一个字段。

有些信息存储在这个结构中,有些像字段名等信息存储在常量池中。

方法集合

方法组件托管由该类显式定义的方法,而不包括从超类继承的任何其他方法。

同样,其开始的前两个字节是该类或者接口中包含的方法计数,其余的是包含每个方法结构的可变长度数组。

每个方法结构包含了有关方法的参数列表,返回值类型,方法的局部变量所需的堆栈单词数,方法操作数堆栈所需的堆栈单词,异常表,字节码序列等信息。

属性集合

属性部分包含有关类文件的几个属性,比如其中一个属性是源代码属性,它显示编译该类文件的源文件的名称。

同样属性部分的前两个字节是属性数量的计数,然后是属性本身。jvm将忽略它们不理解的任何属性。

总结一下

这里我简单的参照JVM规范说明了一下我们编写Java源代码后编译成的.class文件内容,它被JVM的类加载模块加载后,会形成上面的数据结构,了解这个结构,主要是为以后我们在Java编程中如何去理解其处理过程,代码优化,性能调优,包括反射机制如何读取等都有帮助。


本文是我的《Java高级编程基础》系列的第四篇,由于Java高级编程主要是以对底层特别是Java虚拟机的理解为基础的,虽然网上有很多专业的文章已经针对这一块讲的非常好了,我还是想从另外一个角度去重复串联一下它们,希望能够为想学习Java开发的小伙伴们提供另外一个理解它们的思路和学习路线图。期待与大家一起共同探索一条Java学习的高效之道,下面是前面几篇相关的文章,欢迎同行评论交流!

《Java高级编程基础:深入理解虚拟机的共享堆内存和方法区》

《Java高级编程基础:详细解读虚拟机底层栈帧线程模型》

《Java高级编程基础:可以从这个思路去理解JVM和性能调优》

java主类与源代码名称_Java高级编程基础:类文件结构解析,看穿Class代码背后的秘密...相关推荐

  1. java主类与源代码名称_java 获取操作系统名称 附源代码

    记得刚入手 在写跨平台代码的时候,有一些操作是与平台有关的,这个时候,准确地了解软件是在哪个平台下进行工作就显得非常重要了,知道了软件在哪种类型的操作系统下工作,分别编写不同的代码,就可以让整个软件地 ...

  2. java睡眠后继续执行_Java高级编程基础:如何使用线程的休眠,中断和连接

    线程休眠和中断 我们知道了在编程过程中创建线程,并启动以后,线程会交由操作系统来管理调度执行一个我们指定的计算任务. 如果没有其它异常情况出现的话,它会持续运行直到我们实现的run()方法执行完毕为止 ...

  3. 信号量的实现和应用实验报告_Java高级编程基础:原子信号量操作实现组线程执行管理...

    前言 由于Java高级并发编程主要是针对多线程并发访问公共资源控制来展开的,而现在服务器大多都是多核处理,所以在执行控制中,单一的同步锁无法满足需要,为此Java 1.5开始引入了三个重要的概念Sem ...

  4. java 读取硬件设备发送数据_Java网络编程基础

    1.软件结构 C/S结构:全称为Client/Server结构,是指客户端和服务器结构.常见程序有QQ.迅雷等软件. B/S结构:全称为Browser/Server结构,是指浏览器和服务器结构.常见浏 ...

  5. python 元类 type_python Class:面向对象高级编程 元类:type

    type的用法: 1.普通的type用法:检查类型class my(object): def hello(self, name='world'): print('Hello, %s.' % name) ...

  6. java 枚举高级应用_java高级编程之枚举

    接下来本章的重点来了,就是枚举: 枚举是什么? Java中的枚举是一种特殊的类,不但可以添加字段,构造方法,普通方法,甚至可以添加main()方法, 为什么需要枚举? 一些方法在运行时,它需要的数据不 ...

  7. java高效获取内部类属性值_Java高级特性:内部类

    内部类是什么 内部类是指在一个外部类的内部再定义一个类.内部类作为外部类的一个成员,并且依附于外部类而存在的.内部类可为静态,可用protected和private修饰(而外部类只能使用public和 ...

  8. java对象赋值给另一个对象_java面向对象编程

    对象,从字面意思来看就是我们面对的物象.由此便可以知道,万事万物皆为对象.比如:一台电脑,一辆汽车,一部手机等等都是对象. 比如我们想要买一部手机,我们想要内存大一点的,最新款的,CPU 运算快一点的 ...

  9. JAVA mac系统io文件流_Java IO流基础1--IO的分类体系与文件流

    什么是IO流 Java中的IO 了解什么是IO流之前,要先知道什么是IO.IO,就是in和out(即输入和输出),指应用程序和外部设备之间的数据传递,常见的外部设备包括文件.管道.网络连接等. 流的概 ...

最新文章

  1. python爬虫scrapy步骤mac系统_Mac中Python 3环境下安装scrapy的方法教程
  2. VSCode中屏蔽文件files.exclude和屏蔽文件搜索search.exclude
  3. vue树形多列_[vue]使用Element-ui的el-table实现多列同时排序。
  4. ios 不同的数据类型转化为json类型
  5. Github使用初体验2018.08.07
  6. Java ACM模式
  7. Qt工作笔记-对qmake的认识【两篇转载结合】
  8. 2pin接口耳机_让耳机“轻松一下”—— QDC BTX(耳机蓝牙线)
  9. 【读书笔记-诗词歌赋】诗词积累(一)
  10. 从northwind中查询Products中最高单价(UnitPrice)是多少;
  11. 两家“国网”合建5G,三大运营商“好日子到头”?
  12. 塑胶模具注射分类有哪大几类?
  13. Python 绘制数据图表
  14. wtc java 代码 tpcall(servicename_WebLogic下WTC Service的配置
  15. microbit积木块菜单图标
  16. html 进入页面延迟加载数据,跳转至预加载的页面后数据显示延迟问题
  17. matlab之ma q 模型,matlab之ARMA(p,q)模型
  18. error: Libtool library used but 'LIBTOOL' is undefined
  19. vue iframe 宽高自适应
  20. 雪花,是冬天凝固的泪

热门文章

  1. nginx,tomcat,apache三者分别用来做什么,有何区别
  2. Python Selenium 常用方法总结
  3. ubuntu无法安装vscode(visual studio code)如何卸载snap?
  4. python struct.calcsize()函数(返回格式字符串fmt描述的结构的字节大小)
  5. linux——用脚本实现全自动安装虚拟机
  6. 反射中getDeclaredConstructors和getConstructors两个方法的区别,然后setAccessible什么时候用,作用是什么?
  7. X-Magic Pair gcd,剪枝(1600)
  8. html文字自动上翻,jQuery超酷文字随机翻转变换动画特效
  9. python求乘积内建函数_Python中的内建函数(Built_in Funtions)
  10. java jdk安装失败 mac_Mac javaJDK安装遇到的坑和环境变量配置2019-07-09.