写在前面,Java之所以能成为平台无关的语言,虚拟机立大功,我们只需要与虚拟机进行交互,至于和操作系统什么的事情,那都是虚拟机的工作;而虚拟机是怎么做到这些事情的呢?字节码class文件是关键,正是由于有这个东西,Java实现了“一次编写,到处运行”。
Class文件中包含虚拟机指令集、符号表以及其他辅助信息。
Java虚拟机没有与任何一门语言绑定,它只认class文件,换句话说不管什么语言,只要能编译成class文件,就能执行。
Class类文件的结构
任何一个class文件都对应一个类或接口的定义信息。
Class文件是一组以字节为基础单位的二进制流,数据严格按照顺序排列,中间没有任何分隔符。
Class文件格式采用一种类似于c语言结构体的伪结构来存储,这种伪结构中只有无符号数和表两种数据类型。
无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个
字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串
值。
表由多个无符号数或其他表作为数据项构成的符合数据类型。
当需要描述同一类型但数量不定的多个数据时,会使用一个前置的容量计数器加若干个连续的数据项的形式。
这就是对于class文件结构的详细描述。
魔数与class文件的版本
每个class文件的头4个字节被称为魔数,它的唯一作用是确定这个文件是否为一个能识别的class文件。并且魔数取的很有浪漫气息:0xCAFEBABE;紧接着魔数的四个字节是class文件的版本号;第5和6字节是次版本号,第7、8字节是主版本编号。
常量池
常量池是class文件里的资源仓库,他还是在class文件中第一个出现的表类型数据项目。
由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count) ,这个容量计数是从1开始的。如下图所示:常量池容量(偏移地址: 0x00000008) 为十六进制数0x0013,则十进制为19,由于0(0算一个值)空出来,则这里有18个长常量索引范围为1-18,在Class文件格式规范制定之时, 设计者将第0项常量空出来是有特殊考虑的, 这样做的目的在于, 如果后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义, 可以把索引值设置为0来表示。
常量池中主要存放两大类常量:字面量和符号引用
字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,主要包括下面几类常量:·被模块导出或者开放的包(Package)
·类和接口的全限定名(Fully Qualified Name)
·字段的名称和描述符(Descriptor)
·方法的名称和描述符
·方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
·动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段最终 在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用的。当虚拟机做类加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
通俗来说,符号引用的作用是在类加载时由符号引用->直接引用(访问地址入口),这样这个方法或什么东西才能被虚拟机真正正常使用。
访问标志
在常量池的字节信息结束后,紧接着是代表访问标志的两个字节,这个标志主要用于记录一些类或者接口的访问信息,如:这个class是类还是接口的文件,是否被定义为public类型、是否是abstract类型,是类的话,是否被声明为final即是否能继承等等。大概就是类似下图的东西。