类加载  

  类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产出是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向我们提供了访问方法区内的数据结构的接口。

生命周期

  其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

 

加载

  查找并加载类的二进制数据加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: 通过一个类的全限定名来获取其定义的二进制字节流。 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 * 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

  加载方式:

    1. 命令行启动应用时候由JVM初始化加载

    2. 通过Class.forName()方法动态加载

    3. 通过ClassLoader.loadClass()方法动态加载

  Class.forName()和ClassLoader.loadClass()加载的区别: Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块; ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。 * Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

验证

  验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  验证阶段大致会完成4个阶段的检验动作:

    1. 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型等等。

    2. 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外、这个类的父类是否继承了不允许被继承的类等等。

    3. 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

    4. 符号引用验证:确保解析动作能正确执行,如果无法通过符号引用验证,那么将会抛出一个java.lang.IncompatibleClassChangeError异常的子类。

  验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

准备

  当完成字节码文件的校验之后,JVM 便会开始为类变量分配内存并初始化。这里需要注意两个关键点,即内存分配的对象以及初始化的类型。

  内存分配的对象:Java 中的变量有「类变量」和「类成员变量」两种类型,「类变量」指的是被 static 修饰的变量,而其他所有类型的变量都属于「类成员变量」。在准备阶段,JVM 只会为「类变量」分配内存,而不会为「类成员变量」分配内存。「类成员变量」的内存分配需要等到初始化阶段才开始。

  初始化的类型:在准备阶段,JVM 会为类变量分配内存,并为其初始化。但是这里的初始化指的是为变量赋予 Java 语言中该数据类型的零值,而不是用户代码里初始化的值。

  但如果一个变量是常量(被 static final 修饰)的话,那么在准备阶段,属性便会被赋予用户希望的值。

解析

  虚拟机将常量池内的符号引用替换成直接引用的过程。 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

  符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时可以无歧义的定位到目标即可。 直接引用:可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

初始化

  初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

    1. 声明类变量是指定初始值

    2. 使用静态代码块为类变量指定初始值。

  JVM初始化步骤:

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

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

    3. 假如类中有初始化语句,则系统依次执行这些初始化语句。

  虚拟机规范严格规定了有且只有5中情况(jdk1.7)必须对类进行“初始化”(加载、验证、准备自然需要在此之前开始):

    1.遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。生成这四条指令最常见的Java场景是:使用new关键字实例化对象时、读取或设置一个类的静态字段(static)时(被static修饰又被final修饰的,已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法时。

    2.使用Java.lang.refect包的方法对类进行反射调用时(比如:Class.forName(“com.lzt.Test”)),如果类还没有进行过初始化,则需要先触发其初始化。

    3.当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

    4.当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先执行该主类。

    5.当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。

结束生命周期

  在如下几种情况下,Java虚拟机将结束生命周期 执行了System.exit()方法 程序正常执行结束 程序在执行过程中遇到了异常或错误而异常终止 由于操作系统出现错误而导致Java虚拟机进程终止。

 

 


各位看官大佬,不足之处,多多批评指正,不胜感激!