类加载机制和双亲委派模型的探讨

在这里插入图片描述

编译即javac的过程,负责将.java文件编译成.class文件,主要是类型,格式检查与编译成字节码文件;加载是指java*过程,将.class文件加载到内存中去解释执行。类的加载时机在运行时,但并不是一次性全部加载,一般来说一个class只会被加载一次,之后就会从jvm的class实例中获取,不会再去文件系统中加载.class文件了。
java虚拟机中类加载的全过程:加载、验证、准备、解析和初始化这5个阶段执行的具体工作。它的整个生命周期除了这五个阶段外还包括使用和卸载两个阶段。即类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用、和卸载七个阶段,其中验证、准备和解析三个部分统称为连接。
步骤:
在这里插入图片描述

Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能,这里就是我们经常能见到的Class类。
工作机制:
类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误,如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
类加载过程:

  1. 通过类型的完全限定名,产生一个代表该类型的二进制数据流。
  2. 解析这个二进制数据流为方法区内的运行时数据结构
  3. 创建一个表示该类型的java.lang.Class类的实例,作为方法区这个类的各种数据的访问入口。
    通过类型的完全限定名,产生一个代表该类型的二进制数据流的几种常见形式:
    从zip包中读取,成为日后JAR、EAR、WAR格式的基础;
    从网络中获取,这种场景最典型的应用就是Applet;
    运行时计算生成,这种场景最常用的就是动态代理技术了;
    注: 非数组类加载阶段既可以使用系统提供的类加载器来完成,也可以由用户自定义的类加载器去完成。(即重写一个类加载器的loadClass()方法)
    验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
    验证阶段大致上会完成4个阶段的校验工作:文件格式、元数据、字节码、符号引用。
    文件格式验证
    验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。这个阶段验证是基于二进制字节流进行的,只有通过这个阶段的验证后,字节流才会进入内存的方法区进行存储,所以后面的3个阶段的全部是基于方法区的存储结构进行的,不会再直接操作字节流。
    元数据验证
    该阶段对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求,目的是保证不存在不符合Java语言规范的元数据信息。
    字节码验证
    该阶段进行数据流和控制流分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。
    注: 如果一个方法体通过了字节码验证,也不能说明其一定是安全的,因为校验程序逻辑无法做到绝对精确。
    符号引用验证
    最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三个阶段——解析阶段中发生。符号引用验证的目的是确保解析动作能正常执行。
    验证的内容主要有:
    符号引用中通过字符串描述的全限定名是否能找到对应的类;
    在指定类中是否存在符号方法的字段描述及简单名称所描述的方法和字段;
    符号引用中的类、字段和方法的访问性(private、protected、public、default)是否可被当前类访问。
    准备阶段:
    准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配. 这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:符号引用以一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经在内存中。

直接引用 :直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般都不相同,如果有了直接引用,那引用的目标必定已经在内存中存在。

1、类或接口的解析:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。
2、字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束。
3、类方法解析:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。
4、接口方法解析:与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。
类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。
在Java中对类变量进行初始值设定有两种方式:
①声明类变量时指定初始值
②使用静态代码块为类变量指定初始值
初始化步骤
虚拟机规范严格规定了有且只有五种情况必须立即对类进行“初始化”:
使用new关键字实例化对象的时候、读取或设置一个类的静态字段的时候,已经调用一个类的静态方法的时候。
使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则需要先触发其初始化。当初始化一个类的时候,如果发现其父类没有被初始化就会先初始化它的父类。
当虚拟机启动的时候,用户需要指定一个要执行的主类(就是包含main()方法的那个类),虚拟机会先初始化这个类;
而对于接口,当一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口时(如引用父接口中定义的常量)才会初始化。
所有引用类的方式都不会触发初始化的引用叫被动引用:

  1. 通过子类引用父类静态字段,不会导致子类初始化;
  2. 通过数组定义引用类,不会触发此类的初始化
    SuperClass[] superclass=new SuperClass[5];
    3.常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用定义常量的类,因此不会触发定义常量的类的初始化

1、假如这个类还没有被加载和连接,则程序先加载并连接该类

2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

3、假如类中有初始化语句,则系统依次执行这些初始化语句
初始化阶段时执行类构造器()方法的过程。
类加载器:启动类加载器,扩展类加载器,应用程序类加载器
双亲委派模型
如果一个类加载器接受到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子类加载器才会尝试自己去加载 。

猜你喜欢

转载自blog.csdn.net/weixin_42373873/article/details/89190917