描述加载中,JVM类加载的过程分析
本文主要介绍JVM类加载的过程,具有很好的参考价值。我希望它会帮助你。如果解释有误或没有充分考虑,请留言指出,谢谢!
一个类的整个生命周期从加载到虚拟机内存到卸载内存开始。它的整个生命周期包括:加载、验证、准备、解析、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。验证、准备和分析三部分统称为Linking。这七个阶段的发生顺序如图所示:
Loading—-将二进制字节流加载到方法区
“加载”是“类加载”过程的一个阶段。在加载阶段,虚拟机需要做以下 3 件事:
1)通过类的全限定名,在网络中,运行时动态生成(如动态代理技术),由其他文件生成(如JSP文件生成对应的Class类等)。
2)将这个字节流表示的静态存储结构转化为方法区的运行时数据结构。
3)在内存(方法区)中生成一个表示该类的java.lang.Class对象,作为该类在方法区的各种数据的访问入口。
与类加载过程的其他阶段相比,非数组类的加载阶段(准确的说是加载阶段获取类的二进制字节流的动作)是开发者最可控的. ,因为加载阶段既可以由系统提供的引导类加载器完成,也可以由用户定义的类加载器完成。开发者可以定义自己的类加载器来控制字节流的获取方式(即覆盖类加载器的 loadClass() 方法)。
加载阶段完成后,将虚拟机外的二进制字节流按照虚拟机要求的格式存储在方法区。方法区的数据存储格式由虚拟机实现定义。机器规范没有指定该区域的具体数据结构。然后在内存中实例化一个java.lang.Class类的对象(在Java堆中并没有明确指定。对于HotSpot虚拟机来说,Class对象比较特殊。虽然是对象描述加载中,JVM类加载的过程分析,但是存储在方法区) ,该对象将作为程序访问方法区中这些类型数据的外部接口。
验证
验证是连接阶段的第一步。这一步的主要目的是保证class文件的字节流中包含的信息满足当前虚拟机的要求,不会危及虚拟机本身的安全。
验证阶段主要包括四个验证过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
文件格式验证
验证字节流是否符合Class文件格式规范,是否可以被当前版本的虚拟机处理。
例如:是否以幻数0xCAFEBABE开头,主次版本号是否在当前虚拟机的处理范围内,常量池常量中是否存在不支持的常量类型等。
这个验证阶段的主要目的是保证输入的字节流能够被正确解析并存储在方法区,并且格式满足描述一个Java类型信息的要求。此阶段的验证基于二进制字节流。只有在这个阶段验证通过后,字节流才会被存入内存的方法区。
元数据验证
这个阶段是对字节码所描述的信息进行语义分析,以保证所描述的信息符合Java语言规范的要求。验证点可能包括:
字节码验证
执行数据流和控制流分析。在这个阶段,对类的方法体进行验证和分析。此阶段的任务是确保验证类的方法不会在运行时执行危及虚拟机安全的行为。
符号引用验证
符号引用校验可以看成是常量池中各种符号引用的信息进行匹配校验,通常需要校验以下内容
准备—-为类变量分配内存并设置初始值
准备阶段是为类变量正式分配内存,并设置类变量的初始值,在取值阶段,这些变量使用的内存会分配到方法区。
现阶段容易引起混淆的知识点有两个,
首先,此时的内存分配只包括类变量(静态修改变量)描述加载中,不包括实例变量,实例变量会在对象实例化时与对象一起分配在java堆中。二是这里所说的初始值“通常”是数据类型的零值。假设一个类变量定义为:
公共静态 int 值 = 123;
那么变量准备阶段后value的初始值是0而不是123,因为还没有执行java方法,而赋值123的putstatic指令存放在类的constructor()方法中程序被编译。所以给123赋值的动作要到初始化阶段才会执行。
在上面提到的“正常情况”下,初始值为零。与一些特殊情况相比,如果类字段的字段属性表中存在ConstantValue属性,则变量值将处于准备阶段。它被初始化为ConstantValue属性指定的值,假设上面的类变量值定义为:
public static finalint value = 123;
javac会在编译时为value生成ConstantValue属性,在准备阶段,虚拟机会根据ConstantValue的设置将该值设置为123。
解析—-将常量池中的符号引用转化为直接引用
解析阶段是将虚拟机常量池中的符号引用替换为直接引用的过程。
符号引用:符号引用是一组符号来描述被引用的目标对象。符号可以是任何形式的文字,只要它可以用来定位目标而没有歧义。符号引用与虚拟机实现的内存布局无关,引用的目标对象不一定加载到内存中。
直接引用:直接引用可以是直接指向目标对象的指针、相对偏移量或可以间接定位到目标的句柄。直接引用与虚拟机内存布局的实现有关。从不同虚拟机实例上的相同符号引用转换而来的直接引用通常是不相同的。如果有直接引用,则被引用的目标必须已经存在于内存中。
解析的动作主要针对四种符号引用进行:类或接口、字段、类方法、接口方法。分别对应编译常量池中的四种常量类型CONSTANT_Class_Info、CONSTANT_Fieldref_Info、CONSTANT_Methodef_Info、CONSTANT_InterfaceMethoder_Info。
类或接口解析
字段分辨率
类方法解析
接口方法解析
初始化- —初始化类变量和静态语句块
在这个阶段,类中定义的Java程序代码(或字节码)被实际执行。
在准备阶段,类变量已经被赋予了一次系统所需的初始值描述加载中,JVM类加载的过程分析,在初始化阶段,类变量和其他资源根据程序员通过程序做出的主观计划进行初始化,或者可以换个角度来表达:初始化阶段就是执行类构造函数()方法的过程。
注意:是类constructor()而不是power constructor()
以下五种情况,类必须立即初始化(同时加载、验证、准备自然需要在此之前):
当遇到new、getstatic、putstatic或invokestatic这四个字节码指令时,如果类还没有被初始化,则必须先触发其初始化。产生这4条指令的最常见的java代码场景是:使用new关键字实例化一个对象,读取或设置一个类的静态字段(除了被final修饰的静态字段已经被编译器)),以及调用类的静态方法时。使用java.lang.reflect包的方法对类进行反射调用时描述加载中,在初始化类时,如果发现其父类尚未初始化,则需要启动其父类的初始化班级。当JVM启动时,用户指定一个执行的主类(包含main方法的类),虚拟机会首先初始化这个类。使用JDK1.7的动态语言支持时,如果java.lang.invoke.MethodHandle实例最终解析结果为REF_getStatic,则REF_putStatic和REF_invokeStatic的方法句柄,该方法句柄对应的类有没有被初始化。类构造函数和强度构造函数()的区别
() 类构造方法是在jvm中第一次加载,在调用类文件时调用。因为它是在类级别,所以它只加载一次。它是由编译器自动收集类中的所有类变量(静态修改变量)和静态语句块(static{})生成的。收集器的收集顺序由程序员在源文件中编写代码的顺序决定。
() 实例构造方法,在实例创建时调用,包括调用new操作符;调用 Class 或 java.lang.reflect.Constructor 对象的 newInstance() 方法;调用任何现有对象的 clone() 方法;由 java.io.ObjectInputStream 类的 getObject() 方法反序列化。
参考深入了解Java虚拟机-JVM类加载机制(类加载过程和类加载器)
Java虚拟机:类加载的5个进程
评论前必须登录!
注册