虚拟机的类加载机制

虚拟机吧描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化、最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类从被加载到虚拟机内存开始,到卸载出内存为止,他的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载这7个阶段。

除了解析阶段以外,其他阶段必须按部就班的按照顺序开始,解析阶段可以在初始化之后开始。

类加载的加载阶段

在加载阶段,虚拟机要完成以下事情:

1.通过一个类的全限定名来获取定义此类的二进制字节流。

2.将整个自己留所代表的的静态存储结构转化为方法区的运行时数据结构。

3.在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。

类加载的验证阶段

验证阶段大致会分为4个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

文件格式验证:验证Class文件是否规范,并且是否能被当前版本的虚拟机处理。

元数据验证:验证Class文件里面的信息是否符合Java语言的规范的要求。如:这个类是否有父类、父类是不是final的等。

字节码验证:验证程序的语义是否合法,符合逻辑。

符号引用验证:验证引用的正确与否。如:能否通过这个引用找到类,找到的类正不正确,访问权限是否可以被访问等。

类加载的准备阶段

正式的为类变量分配内存,并设置初值。一般情况下初值为零值,只有在final时不是零值。

类加载的解析阶段

解析阶段是虚拟机将常量池中的符号引用转为直接引用的过程。

符号引用:是一组符号来描述所引用的目标,符号引用可以使任何形式的字面量,只要使用时能无歧义的定位到目标即可。

直接引用:是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符。

类或接口:在A类中,有一个类或接口的引用,这个引用的类型为B,那么如果B类不是数组,虚拟机会使用A类的类加载起去加载B类,如果B是一个数组,并且元素为对象,那么数组里面的元素,会按照前面的规则去加载。

字段解析:首先解析这个字段所属的类或接口A。如果A中包含这个字段,返回这个字段的引用,并结束查找,否则查找A类实现的接口中是否有这个字段,否则查找A类继承的父类中是否有这个字段,否则跑出异常。

类方法解析:首先解析这个方法所属的类,如果不是类而是接口,抛出异常。是类,在这个类中查找是否有这个方法,如果没有查找A类继承的弗雷中是否有这个方法,否则查找是否为抽象方法,是的话抛出异常,否则平抛出另一种异常。

接口方法解析:首先解析着个方法所属的接口,如果是类,抛出异常。是接口,在这个接口中查找是否有这个方法,如果没有查找父接口,如果还没有抛出异常。

类加载的初始化阶段

初始化是类加载的最后一步,在初始化阶段会执行<clinit>(),这个方法是编译器自动收集类中所有类变量的赋值动作和静态语句块中的聚居合并而成的。

必须立即对类进行初始化的5中情况:

1.在使用new关键字实例对象、读取或设置类的静态变量、调用类的静态方法时,类没有初始化,则需要先触发其初始化。

2.对类进行发射调用的时候。

3.当初始化一个类时,他的父类没有被初始化,则父类要被初始化。

4.虚拟机启动时,包含main()的类要被初始化。

5.使用方法句柄时,这方法句柄所在的类没有初始化,则这个类要被初始化。

除此之外所用其他引用类的方式都不会触发类的初始化。

如:调用子类中没有的父类的静态变量时,子类不会被初始化。通过数组定义这个引用类时,不会初始化。调用常量,不会初始化。

类加载器

类加载器是实现了通过一个类的全限定名来获取描述这个类的二进制字节流的动作的代码块。

比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。这里的相等指,Class对象的equals方法、isAssignableFrom()、isIstance()的返回结果和Instanceof的关系判定结果。

在java虚拟机的角度来讲,只存在两种不同的类加载器:第一种启动类加载器,第二种是其他类加载器。

在java开发人员的角度来看,类加载器还可以在分的更细致一点:

启动类加载器,扩展类加载器,应用程序类加载器。

双亲委派模型工作过程:类加载器收到类加载的请求,自己不干,调用自己的父类,直至最顶层,如果父类不能完成,在由自己加载。

运行时栈帧结构

适用于支持虚拟机进行方法调用和方法执行的数据结构。栈帧中存储了方法的局部变量表、操作数栈、动态链接和方法返回地址。

局部变量表

局部变量表是一组变量值存储空间,用于存放方法阐述和方法内部定义的局部变量。

操作数栈

操作数栈是一个先进后出的栈,当一个方法执行时,这个栈是空的,在方法的执行过程中,会有各种季节吗指令向操作数栈中写入和提取内容,这就是出栈和入栈操作。

动态连接

每个栈帧中都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。

方法调用

方法调用不等同于方法执行,方法调用极端唯一的任务就是确定被调用方法的版本。

在解析阶段中便确定了调用的版本,包括静态方法、私有方法、构造器、父类方法。

静态多分派和动态单分派

静态分派是根据静态类型和方法参数进行分派。

动态分派是根据实际类型进行分派。

猜你喜欢

转载自www.cnblogs.com/xiaosuye/p/10291517.html