类文件
结构
Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑排列在文件当中,中间没有任何的分隔符,这使得整个Class文件当中存储的内容几乎完全都是程序运行的必要数据。
当Class文件需要占用8个字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
根据《虚拟机规范》的规定,Class文件格式采用类似C语言结构体的伪结构来存储数据。这种伪结构当中只有两种数据类型:“无符号数”和“表”。
无符号数属于基本数据类型,以u1,u2,u4,u8来分别代表一个字节,两字节,四个字节,八个字节。
表是由多个无符号数或者其他表作为基本数据项来构成的复合数据类型,为了便于区分都以_info来结尾。
魔数
每个Class文件开头的头四个字节被称为魔数,它的唯一作用是来确定这个文件是否是一个能被虚拟机接受的Class文件。
JVM的Class文件魔数是 CAFEBABY (咖啡宝贝)
版本序号
紧接着魔术的第五个第六个字节描述的是次版本 第七个第八个字节是主版本序号
常量池
紧接着版本序号之后便是常量池入口,常量池可以比喻为Class文件的资源仓库,它是与Class文件其他项目关联程度最大的数据,通常也是占用CLass文件空间最大的数据项目之一,因为常量池的数据量不固定,所以在他的开头使用一个u2两个字节数据来描述常量池的数据量,代表常量池容量计数值。
常量池通常会存放两大常量分别是:字面量和符号引用。
常量池在进行Javac编译的时候,并不会直接连接,而是进行动态连接,也就是说在Class文件当中不会保存各个方法在内存当中的布局信息,这些字段会在进行类加载的时候进行由符号引用来解析出直接引用通而得到详细的内存地址来使用。
访问标志
在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息。通常包括这个Class是类还是接口,是否定义为Public类型,是否为抽象类型,若是public类又是不是被声明为final等等。
类索引,父类索引与接口索引集合
他们都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的结婚。Class文件由这三项数据来确定该类的继承关系。
字段表集合,方法表集合
字段表集合大同小异也有着自己的标志位和对应含义,是一个u2的字节
方法表与字段表相同,他的结构依次为:访问标志,名称索引,描述索引,属性表集合几项
类的加载
类的加载分为下面七个阶段:
- 加载
- 验证
- 准备
- 解析
- 初始化
- 使用
- 卸载
这七个阶段按顺序发展,这既是类的什么周期
初始化阶段
一般下面这几种情况触发类的初始化阶段:
- 遇到new,getstatic,putstatic或者invokstatic这四条字节码指令,若是类型没有进行初始化,则会进行初始化阶段。
- 使用反射调用的时候
- 初始化类的时候,其父类还没有初始化
- 虚拟机启动的时候,用户需要指定一个执行的主类
- 当使用JDK 7加入新的动态语言支持时
- 当一个接口定义了JDK 8新加入的默认方法的时候。
仅仅对于这六种才会触发初始化的场景。
对于静态字段来说,只有直接定义这个字段的类才会初始化,通过子类调用父类的静态字段,父类会被初始化,子类不会。
加载
在加载过程,Java虚拟机会做三件事情:1,通过一个类的全限定名来获取类的二进制字节流。2,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。3,在内存当中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。
验证
验证这一阶段是确保Class文件的字节流文件中包含的信息符合《Java虚拟机规范》当中的全部约束,确保这些信息不会危害Java虚拟机,比如说他会检查魔数是否正确,版本号是否融洽,编码是否正确,常量池当中的常量类型是否支持等。
准备
准备阶段是为类当中定义的变量(静态变量)分配内存并且设置类变量初始化的阶段。
解析
解析阶段是Java虚拟机将常量池当中的符号引用替换为直接引用的过程。
符号引用是以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要可以无歧义的定义到变量就可以
直接引用是可以直接指向目标的指针,相对偏移量或者是一个间接定位到目标的句柄
初始化
初始化时类加载的最后一个步骤,直到初始化阶段,Java虚拟机才开始真正执行类当中编写的Java程序代码,将主导权交给应用程序。
用直接的方式表达,初始化阶段就是执行类构造器<clinit>()的过程。
<clinit>()
是JVM自动生成类初始化的方法,主要作用为执行类的静态代码块和静态字段的初始化。
特点:1. 类加载到JVM的时候自动执行,并且只执行一次
- 在类的任何实例被创建前执行
- 多线程会加锁,<clinit>()只会被一个线程执行,其他线程会阻塞等待
- 无返回值,不接受任何参数
- 不接受任何显示的调用也不能被子类覆盖继承
若是类当中有多个静态代码块或是静态字段,JVM会将这些将初始化,代码合并到一个clinit方法当中,按照源文件的顺序执行。
类加载器
对于任何一个类,都必须由它的类加载和这个类本身一起共同确立其在Java虚拟机当中的唯一性,每一个类加载器都有他独立的一个类名称空间。也就是说即使这两个类来自同一个Class文件,创建他的类加载器不同,那么这两个类就不相同。(也就是equals方法等)
双亲委派模型
站在Java虚拟机的角度来看,只存在两种不同的类加载器,一种是启动类加载器,是虚拟机自身的一部分,另外一部分是其他所有类的加载器,这些类加载器,这些加载器都由Java语言实现,是独立于虚拟机外部,并且全都继承于java.lang.ClassLoader
双亲委派模型的工作过程是,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试记载这个类,而是会把这个类委派给父类加载器实现,只有当父类加载器不能完成的时候,才会自己去尝试加载。使用这种模型的好处是类加载器有了优先级的层次关系。比如说Java.lang.Object类,要是进行类加载,无论哪个类要加载这个类,都要委派给Object类进行完成,因此Object类在程序的各种类加载器环境下都可以保证是同一个类。