JVM虚拟机之类加载机制
在本节中,学习的是Class文件如何进入虚拟机。
类从加载到卸载分成七个阶段。加载,验证,准备,解析,初始化,使用,卸载。其中的验证,准备,解析统称为连接。
对类的初始化常常是建立在类或子类被直接使用的情况下,如new,反射调用,子类被使用时,启动类,静态方法的调用。其他情况一律不考虑初始化。
类加载
首先对类进行加载,通过全限定名进行类的二进制字节流获取,之后将字节流的存储结构化成方法区的数据结构,存储在方法区中,生成Class对象,作为以后使用其方法的入口。
对于二进制字节流的获取并不严格,因此可以从各种途径获取,此处不进行介绍。
对于数组的加载比较特殊,因为数组是由虚拟机直接生成的,但数组的元素与普通对象一致,生成过程并无不同。
验证
验证阶段需要保证Class文件符合要求。
1.基本格式验证
例如Magic Number,版本号,是否符合UTF-8编码,文件缺不缺东西之类的、
2.元数据验证
顾名思义,进行语义校验,例如有没有父类,父类是Final类之类的。
3.字节码验证
字节码验证是最复杂的阶段,他需要保证字节指令的正确性,例如,不会随便跳转,不会指令操作错误等,如前文中的StackTable就是用来字节码检验的,它将对字节码的推导转换成了对StackTable的检验,它保证了操作栈与本地变量表在某一块区域开始时处在正确的位置。
4.符号引用验证
保证符号引用是对的,能找到,也能被访问,方法等也都存在。虚拟机参数提供了取消验证的参数。
准备
准备动作是将类中的变量在方法区中分配内存并进行赋值,初值全部为0,由ConstantValue属性表的按照表中数据进行赋值。
解析
通过解析将长长的符号引用变成指针类型的直接引用。对于已经解析的符号引用也提供的缓存标记,在下次可以直接使用,invokedynamic指令的除外,因为是动态的,每次都需要重新解析。
其他类或接口的符号引用解析
先解析其他的类或接口,这时会触发其他类的验证,其他类又会触发其他类,如此反复,一个出现错误全错。如果想要变成对数组的引用,比如int数组,那么按照之前的,对Integer进行验证,之后由虚拟机自己生成一个数组。
字段解析
对于字段,首先查找字段表,找到所属类,对类进行解析,之后根据类中数组,查找有无该字段,返回直接引用,如果没有,之后会找接口,父类,从下到上,全都没有失败结束。
返回之后,如果这个字段你没有访问权限的话也是失败的。抛出异常。
对于类与接口方法的解析其实是差不多的,首先不允许方法所对应的类不同,类方法对应的不允许是接口,接口方法对应的类不允许是类。之后,在本类(接口)与父类(父接口)进行查找。最后进行访问权限检查,接口不需要进行该检查。
类与接口方法解析
两种解析是差不多的。
1.查询方法表中的本类。类方法表中的本类必须是类,不能是接口。接口方法表中的本类必须是接口,不允许是类。
2.查询本类(接口)中是否有该方法。
3.查询父类(父接口)
类要进行访问权限检查。接口并不需要。
初始化
在初始化的时候会根据变量赋值代码与static语句自动生成方法。
clinit方法会自动隐式的调用父类的clinit方法。对于接口与接口的实现类,并不会主动调用父类clinit方法,只有用到的时候才会调用。
对于static语句块只能访问语句块之前的,定义在之后的只能赋值不能访问。
同时,初始化保证了多线程的同步。
-----------类加载器
类加载器用在类加载的部分。是Java的创新。
类加载器的作用在于加载类,确认类的唯一性。
这个唯一性在于,如果不是同一类加载器加载出来的,一定不是同一类,即使Class文件一模一样。
双亲委派模型
类加载器只有两种:Booststrap(启动类加载器),与其他(基于代码实现)。
BoostStrap在HotSpot中采用C++实现,其他虚拟机通过Java代码实现或者JNI调用C语言实现。
对于开发人员细分为三种:
BoostStrap启动类加载器:
仅根据文件名识别指定目录(lib)下的类加载到内存当中,由于是虚拟机内部的,因此无法被直接调用,如果你想让这个类被BoostStrap加载,直接加载请求为null即可。
扩展类加载器
加载lib/ext下的类库。
应用程序类加载器
getSystemClassLoader的返回值,为默认加载器。检索ClassPath路径下的类。
根据双亲委派模型,所有的类加载行为都默认优先由父类进行加载。若要自定义类加载器。需要重写findclass函数。
OSGI:破坏了双亲委派模型。变成了网状模型,优先调用父类,在父类都查询不到的时候会进行同级的类加载器进行,因此不符合双亲委派。
对于加载SPI代码时,也破坏了双亲委派模型,需要线程上下文类加载器进行逆向的使用类加载器。