虚拟机类加载机制
1.虚拟机的类加载机制
虚拟机将描述类的数据从class文件加载到内存当中去,并对数据进行校验,转换解析和初始化,最终形成了可以被虚拟机执行的java类型,这就是虚拟机的类加载机制
2.类的生命周期
一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况,其连接的过程又被细分为验证,准备和解析三个小过程。
2.1类加载
在加载阶段,虚拟机需要完成三件事。
1.通过一个类的全限定名来获取定义此类的二进制字节流文件。
2.将这个字节流中所代表的静态存储转化为方法区中运行时的数据结构。
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
2.2类加载器
类加载器在java中主要执行类的加载过程,但是它的作用绝对不仅只是局限于此,在java中,判定一个类是否“相等”,是由加载这个类的类加载器和这个类本身决定的。只有这两个类来自同一个Class文件和由同一个类加载器加载的前提下才有意义,否则这两个类不相等。
从java虚拟机的角度上看有两种类加载器,一种是启动类加载器(Bootstrap ClassLoader)这个类加载器是使用C++实现的,是虚拟机的一部分;另一种是所有其他的类加载器,这种类加载器是由java语言实现的,独立于java虚拟机外部,并且全部继承于java.lang.ClassLoader
从java开发人员的角度看,分为了三种类加载器,分别是启动类加载器,扩展类加载器,还有应用程序类加载器。
2.2.1启动类加载器(Bootstrap ClassLoader)
这个类加载器负责将在<JAVA_HOME>/lib中,或者是被-Xbootclasspath指定的路径中的,并且被虚拟机识别的类库加载到虚拟机内存中。
2.2.2扩展类加载器(Extension ClassLoader)
这个类加载器负责加载<JAVA_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。
2.2.3应用程序类加载器(Application ClassLoader)
由于这个类加载器是ClassLoader中的getSystemClassLoader()中返回值,所以一般称为系统类加载器,他负责加载用户路径上的所指定的类库,如果应用程序中用户没有自定义类加载器,一般情况下这个就是系统的默认类加载器。
2.3双亲委派模型
下图中所示的类加载器之前的这种层次关系就是双亲委派模型,双亲委派模型除了顶层的类加载器之外其他的所有类加载器都有父类类加载器,这里的类加载器之间的额父子关系不是使用继承关系,而是使用组合关系
双亲委派模型的工作方式:如果一个类加载器收到了类加载请求的时候,它不会自己去进行类加载操作,而是将这个类加载的请求委派给它的父类加载器去完成这个操作,因此所有的类加载请求都会传到顶层的父类加载器去,只有当父类加载器反馈自己无法完成这个请求的时候(在自己域范围内搜索类),子加载器才会尝试去加载类。
双亲委派模型的优点是,双亲委派模型对于保证java程序的稳定运行有着重要的意义,双亲委派模型的代码都集中在loadclass()方法当中,主要思想是当有类加载请求的时候,首先检查是否已经被加载,如果没有被加载过就调用父类的loadclass()方法,如果父类加载失败,抛出ClassNotFoundException异常。
2.3验证
验证是连接阶段的第一步,他是确保Class文件字节流中包含的信息符合当前虚拟机的要求。
2.3.1文件格式验证
验证字节流是否符合Class文件格式的规范,并且能被当前的虚拟机所识别
eg:是否含有魔数:0xCAFEBABY...
2.3.2元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求
eg:这个类是否含有父类(所有的类都含有父类)
2.3.3元节码验证
主要目的是通过数据流和控制流分析,确定顺序语义是否合法,符合逻辑
2.3.4符号引用验证
最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段--解析阶段中发生。
2.4准备
准备阶段是正式为类变量分配内存并且设置类变量初始值的阶段,这些变量所使用的内存都是在方法区中进行分配的,内存分配仅仅是针对类变量(被static修饰的),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。
2.5解析
解析阶段就是虚拟机将常量池中的符号引用转化为直接引用的过程。解析包括了1.类或者是接口的解析,2.字段的解析,3.类方法解析,4.借口方法解析。
2.5.1符号引用
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要在使用的时候无歧义的定位到目标就可以。
2.5.2直接引用
直接引用可以是直接指向目标的指针,相对偏移量或者是一个能间接定位到目标的句柄。
2.6初始化
类初始化是类加载的最后一步,在前面类加载的步骤中,都是由虚拟机主导和控制的,但是在初始化阶段便开始真正的执行类中定义的java代码。初始化就是执行类构造器<clinit>方法区别于init()方法
虚拟机规范严格规定有且仅有这5种情况类必须进行初始化操作:
1.遇到了new, getstatic, putstatic, invokestatic这4个字节码指令的时候,如果类没有进行初始化操作,则必须进行初始化操作。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化操作,便对其进行初始化。
3.当初始化一个类的时候,发现其父类没有进行初始化,先对其父类进行初始化操作。
4.当虚拟机启动的时候,用户指定一个要执行的类的主类(main类),虚拟机会先初始化这个类。
5.使用JDK1.7动态语言支持时如果一个java.lang.invoke.MethodHandle实例最后解析结果REF_getStatic, REF_putstatic, REF_invokeStatic方法句柄,并且这个方法句柄所对应的类没有进行初始化,则先触发初始化操作。