四、类文件结构
java源码被编译成字节码文件(.class文件),是一种与体系结构中立的文件,不管是什么语言,只要能编译成字节码文件,jvm就能跑。
1.class文件格式
字节码文件采用一种类似结构体的结构来储存文件。这个文件只有两种数据类型,无符号数和表。
无符号数:以u1、u2、u4、u8来代表1,2,4,8个字节的无符号数,
表:由多个无符号数或者其他表组成的复合数据类型。
以一段helloworld代码为例。
将其编译成字节码文件后,以16进制的方式查看它的字节码文件,结果如下:
接下来,我们来观察字节码文件的内容
魔数
首先字节码文件的前四个字节的内容代表魔数,作用是确定该文件是否可以被jvm接受的格式,java的字节码文件规定,开头四个字节的魔数为0xCAFEBABE(咖啡宝贝?),也就是说只要字节码的前四个字节是0xCAFEBABE,那么这个文件就可以被jvm所执行。
版本号
魔数后面的四个字节为版本号,前两个字节的文件为次版本号,后两个字节为主版本号,可以看到图中的class文件的版本号为0x0034,也就是50。
常量池
在版本号之后便是常量池的相关信息,常量池中主要存放的有以下两种东西
字面量:文本字符串,final常量等
符号引用:类和接口的全限定名,字段的名称和描述符,方法的名称何描述符,方法句柄和方法类型,动态调用点和动态常量。
因为常量池的存储的是字面量和符号引用,这些是由编程人员在编写代码的时候才确定的,其内容数量通常是不固定的,所以字节码文件中的常量池的入口处需要有两个字节来表示常量池中常量数量,如下图所示,我们的helloworld.class文件中的常量池容量为0x001F即31(这里要注意,容量计数是从1开始,即1-30,所以常量池中的常量数量为30),在这个常量池数后面便是各种常量。
访问标志
在常量池结束后的两个字节代表访问标志(一共有16个访问标志位),用以标识类或者接口的层次的访问信息(例如是类还是接口,是否定义为public类,是否为abstract等等)
类索引、夫索引、接口索引
这三种索引中的前两种是一个u2类型的数据,而接口索引集合是一种u2类型的数据的集合。这三种类型的数据用于确定类的继承关系。
字段表集合:
用于描述类中声明的变量(这里不包括在方法中生成的局部变量),字段表中也有和类和接口层次一样的访问标志,用于标识这个字段的作用域、是否被static修饰等等。而字段名和数据类型则是通过引用常量池中的数据来表示。
方法表集合
和字段表类似,由访问标志、名称索引、描述符索引、属性表集合、组成
五、类加载机制
1.类生命周期
2.类加载器
上图中的第一个阶段可分为3步:
1.通过全限定类名去获取定义此类的二进制字节流
2.将字节流中的静态存储结构转化为方法区中的运行时数据结构
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区中这个类的各种数据的访问入口。
而类加载器就工作在这三个步骤中的第一步,即获取二进制字节流,
3.双亲委派机制
在说双亲委派机制前需要先了解三层类加载器的机制,java为我们提供了如下三种类加载器:
1.启动类加载器:负责加载%JAVA_HOME%/lib目录下的或者被-Xbootclasspath参数所指定的类
2.拓展类加载器:负责加载%JAVA_HOME%/lib/ext目录下的类
3.应用程序类加载器:负责加载用户类路径下(classpath下的所有类)
当然除了这三种类加载器外,开发者还可以自定义类加载器。所有类加载器协作起来的流程就叫做双亲委派模型,如下图所示:
双亲委派模型规定,类加载器在收到加载类的请求时,优先将其委派给自己的父加载器去加载,只有当父加载器无法完成加载的时候才会由自己尝试进行加载。