1.java语言与java虚拟机的关系
得益于虚拟机,java被称为跨平台的语言。
同时,java虚拟机也可以被看作是跨语言的平台。java虚拟机不与任何语言(包括java)绑定,它只与Class文件 这种特定的二进制文件强关联。任何语言,只要能编译成符合特定规则的Class文件,就都可以在java虚拟机上运行。如下图1 所示:
图1 java虚拟机与语言的关系
2.Class文件结构
任何一个Class文件都对应着一个类或一个接口的定义信息。但是反过来,类或接口不一定都得定义在文件里,因为还可以动态生成,直接送入类加载器中。
Class文件是一组以8个字节为单位的二进制流,按严格顺序排列,没有任何分隔符。
Class文件采用 “无符号数” 和 “表” 来记录数据。
2.1 无符号数
其中,无符号数是基本的数据类型,u1,u2,u4,u8来分别代表1,2,4,8个字节的无符号数。无符号数可以描述数字,索引引用,数量值或者以UTF-8编码的字符串值。
2.2 表
表是由多个无符号数或者其他表作为数据项构成的数据结构。以_info结尾。整个Class文件本身可视为一张表。
2.3.Class文件记录结构
2.3.1 魔数(magic number)
Class文件的前4个字节是固定的,值为cafebabe.是该字节码文件是否可以被虚拟机接受的标识。
紧接着魔数后的四个字节是java的版本号。《Java虚拟机规范》中明确规定:即使字节码文件结构没有任何改变,也不允许虚拟机执行超过其版本号的Class文件。
2.3.2 常量池
常量池中保存了两大类常量:字面量和符号引用
字面量如:字符串,被声明为final的常量值登录。
符号引用:类和接口的全限定名,字段/方法的名称和描述符、方法句柄和方法类型、动态调用点和动态常量等。
java代码在用javac编译时,没有连接这一步(c/c++有),而是在虚拟机加载Class文件时进行动态连接,即 java中的各个方法、字段等最终在内存中的地址在Class文件中不会有的,当虚拟机做类加载时,会从常量池中获得对应的符号引用,经过在类创建或运行时解析、翻译到具体的内存地址当中。用命令 javap -verbose XX(字节码名称) 可以输出某个字节码中的常量表
2.3.3 访问标志
访问标志用于识别一些类或接口层次的访问信息,如这个Class是类还是接口,是private还是public的等
2.3.4 类索引、父类索引和接口索引集合
该数据是个u2类型的数据集合,Class文件中通过这3项来确定该类型的继续关系。
注:父类索引只有一个,因为java没有多重继续,除了java.lang.Object外,其他所有的类都有父类。
2.3.5 字段表集合
字段表(field_info)描述接口或类中声明的变量。包括类变量或实例变量(不包括方法内部声明的局部变量,局部变量保存在栈中的局部变量表中)。field_info结构图如下,除过图里的,字段表还有 acc_volatile标志(volatile标志),acc_transient标志(transient关键字修饰)
2.3.6 方法表集合
该部分为字段表与方法表的共同部分,除此之外,方法表没有acc_volatile,acc_transient标志,但多了acc_synchronized,acc_native,acc_strictfp和acc_abstract标识。方法里吗的代码存放到字节码文件的Code属性里,Code是方法属性表集合中一个最具扩展性的项目。
方法表集合与上述字段表集合结构类似:
2.3.7 属性表集合
属性表在Class文件、字段表、方法表等都可以有自己的属性表。 属性表与其他结构不同,不再需要遵循严格的属性,只要不与已有属性重名。下图为属性表集合里的code属性。
在Class文件里,Code属性用于描述代码,所有其他的项目都用于描述元数据。
总结:对一个完整的类,看一下字节码结构:
源码:
public class Solution{ public int temp=0; public final long test=1; public void fun1(){ Integer nouse=0; System.out.println("test:"+test); } public static void main(String[] args){ int[] arr={6,1,5,3,2,4}; int[] res=SelectSortArray.selectSort(arr); for(int i:res){ System.out.println(i); } } }
通过javap得出的字节码结构: