当前位置: 首页 > 后端技术 > Java

【JVM知识汇总-9】类加载过程

时间:2023-04-02 00:18:28 Java

【JVM知识汇总-1】JVM内存模型【JVM知识汇总-2】HotSpot虚拟机对象【JVM知识汇总-3】垃圾回收策略与算法【JVM知识汇总-4]HotSpot垃圾收集器【JVM知识汇总-5】内存分配与回收策略【JVM知识汇总-6】JVM性能调优【JVM知识汇总-7】Class文件结构【JVM知识汇总-8】类【JVM知识汇总】-9]类加载的过程[JVM知识总结-10]一个类加载器类的加载过程包括5个阶段:加载、验证、准备、解析和初始化。加载过程“加载”是“类加载”过程的一个阶段,这两个术语不应混淆。在加载阶段,虚拟机需要完成三件事:通过类的完全限定名获取被修改类的二进制字节流,并将二进制字节流表示的静态结构转换为类的运行时数据结构方法区。在内存中创建一个代表该类的java.lang.Class对象作为该类在方法区各种数据的访问接口。获取二进制字节流对于Class文件,虚拟机并没有规定从哪里获取以及如何获取。除了直接从编译好的.class文件中读取外,还有几种方式:从zip包中读取,如jar、war等,从网络上获取,如Applet;通过动态代理技术Stream生成代理类的二进制字节;从JSP文件中生成相应的Class类,从数据库中读取。例如,一些中间件服务器可以将选择程序安装到数据库中,完成程序代码在集群间的分发。“非数组类”和“数组类”加载对比非数组类加载阶段可以使用系统提供的bootstrap类加载器,也可以由用户自定义类加载器完成。开发者可以定义自己的类加载器来控制字节流的获取方式(比如重写一个类加载器的loadClass()方法)。数组类本身不是由类加载器创建的,它是由Java虚拟机直接创建的,然后由类加载器创建数组中的元素类。注意虚拟机规范没有指定Class对象的存储位置。对于HotSpot虚拟机,Class对象比较特殊。虽然是对象,但是存放在方法区。负载阶段与部分连接阶段交错,负载阶段尚未完成,连接阶段可能已经开始。但是,这两个阶段的开始时间仍然保持着固定的先后顺序。验证验证的重要性验证阶段确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不危及虚拟机本身的安全。验证过程文件格式验证:验证字节流是否符合Class文件格式的规范,是否可以被当前版本的虚拟机处理。验证点如下:以magicnumber0xCAFEEBABE开头的主次版本号是否在当前虚拟机的处理范围内内部常量池中是否存在不支持的常量类型。指向常量的索引值是否指向一个不存在的常量。CONSTANT_Utf8_info类型常量是否有不符合UTF8编码的数据。...元数据验证:对字节码描述信息进行语义分析,确保其符合Java语言规范字节码验证:该阶段是验证过程中最复杂的阶段。在运行时没有危及虚拟机的事件。Symbolreferenceverification:该阶段发生在解析阶段,确保解析正常进行。准备准备阶段是正式为变量(或“静态成员变量”)分配内存并设置初始值的阶段。这些变量(不包括实例变量)使用的内部结构都分配在方法区中。初始值“通常”是数据类型的零值(0,null),假设一个类变量定义为:publicstaticintvalue=123;那么准备阶段之后变量值的初始值不是0而是123,所以此时还没有Java方法开始执行。有一个“特例”:如果类字段的字段属性表中存在ConstantValue属性,那么在准备阶段会将该值初始化为ConstantValue垂直指定的值,假设上面的定义类变量值变为:publicstaticfinalintvalue=123;然后在准备阶段,虚拟机根据ConstantValue的设置赋值为123。解析解析阶段是虚拟机引用常量池中符号的过程。初始化类初始化阶段是类加载过程的最后一步,也就是执行类构造函数()方法的过程。()方法是由编译器自动收集类中所有类变量并组合静态语句块(static{})中的语句生成的。编译器集合的顺序由源文件中的语句决定由.在静态语句块中,只能访问静态语句块之前定义的变量,其后定义的变量可以在前面的静态语句块中赋值,但不能访问,如下代码所示。公共类测试{静态{i=0;//给变量赋值可以通过System.out.println(i)正常编译;//编译器会提示“非法前向引用”}staticinti=1;}()方法不需要显式调用父类的构造函数,虚拟机保证父类的()方法在子类的()方法执行之前已经执行。因为父类的()方法先执行,也就是说父类中定义的静态语句块优先于子类的变量赋值操作。staticclassParent{publicstaticintA=1;静态{A=2;}}staticclassSubextendsParent{publicstaticintB=A;}publicstaticvoidmain(String[]args){System.out.println(Sub.B);//输出2}()方法不是必需的。如果一个类没有静态语句块,也没有对类变量的赋值操作,那么编译器就不需要为这个类生成()方法。接口中不能使用静态代码块,但接口也需要通过()方法显式初始化接口中定义的静态成员变量。但是接口不同于类。接口的()方法不需要先执行父类的()方法。只有在父类接口中定义的变量被使用时,父接口才会被初始化。虚拟机确保类的()方法在多线程环境中被正确锁定和同步。如果多个线程同时初始化一个类,只有一个线程会执行这个类的()方法。