java虚拟机类加载机制
class文件(这里的class文件不一定是以存在于具体磁盘的文件形式存在,而是一串二进制的字节流)需要加载到虚拟机中才能使用,虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换,解析,初始化,最终形成可以被虚拟机直接使用的Java类型,即虚拟机类加载机制。
类加载的生命周期如下图所示:
类加载的过程主要是加载,验证,准备,解析,初始化这五个部分,其中,解析的顺序不是固定的,可能是初始化之前,也可能是初始化之后,符合java的运行时动态绑定。
加载:
1.通过一个类的全限定名获取定义此类的二进制流
1.1 通过ZIP包读取,例如JAR,WAR格式
1.2 通过网络获取,例如Applet
1.3 运行时计算生成,例如动态代理技术
2.将字节流代表 的静态存储结构转化为方法区的运行时数据结构
3.在内存中生成代表这个类的java.lang.Class对象,作为方法区访问这个类的各种数据的入口。
验证:
确保Class文件中包含的字节流符合虚拟机规范,不会对虚拟机自身安全带来威胁。
大概分为以下几种验证方式:
1.文件格式验证
例如是否以魔数0xCAFFBABE开头
主次版本号是否在当前虚拟机的处理范围之内
常量池的常量是否有不被支持的类型
2.元数据验证(对类的额元数据信息进行语义检验,确保不存在不符合java语言规范的元数据)
这个类是否有父类(除java.lang.Object)
这个类是否继承了不被允许继承的类(被final修饰的类)
3.字节码验证(通过数据流和控制流分析,对类的方法体校验)
4.符号引用验证
符号引用中通过字符串描述的全限定名是否能够找到相应的类
符号引用中的类,字段,方法的访问性是否可被当前类访问
如若无法通过符号验证,则会报java.lang.IncompatibleClassChangeError异常的子类,例如java.lang.IllegalAccessError,java.lang.NoSuchFieldError等
准备:
为类变量分配内存并设置类变量初始值(这里的初始值是数据类型的零值),如果存在ConstantValue值,例如被final修饰的变量,在准备阶段就会赋给真正的值。
解析:
将常量池中的符号引用替换为直接引用的过程,一般包括类或接口的解析,字段解析,类方法解析,接口方法解析‘
符号引用:用一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能准确定位到目标。与虚拟机的内存分布无关。
直接引用:直接引用可以是直接指向目标对象的指针,相对偏移量或者是一个能间接定位到目标的句柄,直接引用与虚拟机的内存分布是相关的,因此,不同的虚拟机,直接引用一般不同,如果有了直接引用,那么,引用的目标一定在内存中已经存在了
初始化:
执行类构造器< clinit >()方法的过程。
- (1)类构造器< clinit >方法是由编译器自动收集类中所有类变量(静态非final变量)赋值动作和静态初始化块(static{……})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定。静态初始化块中只能访问到定义在它之前的类变量,定义在它之后的类变量,在前面的静态初始化中可以赋值,但是不能访问。
- (2)由于父类构造器< clinit >方法先于子类构造器执行,因此父类中定义的静态初始化块要先于子类的类变量赋值操作。
- (3).类构造器< clinit >方法对于类和接口并不是必须的,如果一个类中没有静态初始化块,也没有类变量赋值操作,则编译器可以不为该类生成类构造器< clinit >方法。
- (4)接口中不能使用静态初始化块,但可以有类变量赋值操作,因此接口与类一样都可以生成类构造器< clinit >方法。
- 接口与类不同的是:
- 首先,执行接口的类构造器< clinit >方法时不需要先执行父接口的类构造器< clinit >方法,只有当父接口中定义的静态变量被使用时,父接口才会被初始化。
- 其次,接口的实现类在初始化时同样不会执行接口的类构造器< clinit >方法。
- (5)java虚拟机会保证一个类的< clinit >方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,只会有一个线程去执行这个类的< clinit >方法,其他线程都需要阻塞等待,直到活动线程执行< clinit >方法完毕。
- 初始化阶段,当执行完类构造器< clinit >方法之后,才会执行实例构造器的< init >方法,实例构造方法同样是按照先父类,后子类,先成员变量,后实例构造方法的顺序执行;需要注意的是,其他线程虽然会被阻塞,但如果执行< clinit >()方法的那条线程退出< clinit >()方法后,其他线程唤醒之后不会再次进入< clinit >()方法。同一个类加载器下,一个类型只会初始化一次。
参考 《深入理解Java虚拟机 JVM高级特性与最佳实践》