首先先来看几个问题java
接下来看看jvm加载class文件的概述:程序员
jvm把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终造成能够被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。这句话差很少已经回答上面三个问题的大部分了。数组
与那些在编译是须要进行链接工做的语言不一样,在Java语言里面,类型的加载和链接过程都是在程序运行期间完成的,这样会在类加载是稍微增长一些性能开销,可是却能为Java应用程序提供高度的灵活性,Java中能够动态的扩展的语言特性就是依赖运行期动态加载和动态链接这个特色实现的。好比编写一个使用接口的应用程序,能够等到运行时在指定其实际的实现。这种组装应用程序的方式普遍应用于Java程序之中。数据结构
类从被加载到jvm内存中开始,到卸载出内存为止,它的生命周期包括了一下步骤:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Useing)和卸载(Unloading)七个阶段。其中的验证、准备和解析三个部分统称为连接(Linking),这七个阶段的发生顺序以下图,注意是发生的顺序,不是执行完成的前后顺序。jvm
加载阶段是“类加载”过程的一个阶段,虚拟机须要作如下三件事:布局
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式有虚拟机实现自定义,虚拟机规范未规定此区域的具体数据结构。而后再Java堆中实例化一个java.lang.Class类的对象,这个对象做为程序访问方法区中的这些类型数据的外部接口。加载阶段与链接阶段的部份内容是交替进行的,加载阶段还没有完成,链接阶段可能已经开始,但这些夹在加载阶段之中进行的动做,仍然属于链接阶段的内容,这两个阶段的开始时间仍然保持着固定的前后顺序。性能
验证阶段虚拟机作了下面这些事情编码
一、文件格式验证spa
第一阶段是要验证字节流是否符合Class文件格式的规范,而且能被当前版本的虚拟机处理。会验证一下这些内容。指针
二、元数据的验证
三、字节码的验证
这个阶段是验证最为复杂的一个阶段,主要工做是进行数据流和控制流分析,紧接第二阶段。
四、符号引用验证
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,注意是初始值不是最终变量的值,都将在方法区中进行分配。若是该变量不是静态变量,将不会进行内存分配,而是会在类出乎实话的时候随着对象一块儿分配到Java堆中。另外这里的初始值一般状况下是零值。具体的初始化的值见下图,图片来源于《深刻理解Jvm虚拟机》。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:符号引用以一组符号来描述所引用的目标,符号可使任何形式的字面量,只要使用时能无歧义地定位到目标便可。符号引用于虚拟机实现内存布局无关,引用的目标不必定已经加载到内存中。
类初始化阶段是类加载过程的最后一步,前面的类加载过程当中,除了在加载阶段用户应用程序能够经过自定义类加载器参与以外,其他动做彻底有虚拟机主导和空值。到了初始化阶段,才真正开始执行类中定义的Java程序代码。
前面讲到在准备阶段变量已经富余过一次初始值,而在初始化阶段,则是根据程序员经过程序制定主观计划去初始化变量和其余资源。
一下四中状况会必须当即对类进行“初始化”。
除了上面4中场景,都不会触发初始化,称为被动引用。
场景一
public class SupClass { public static int value = 100; static{ System.out.println("SupClass init..."); } } public class SubClass extends SupClass { static{ System.out.println("SubClass init..."); } }
客户端代码
public class InitTest { public static void main(String[] args) { System.out.println(SubClass.value); } }
输出以下
SupClass init...
100
能够看到经过子类引用父类的静态字段,不会致使子类初始化。
场景二
其余代码同场景一,客户端代码变成以下
public class InitTest { public static void main(String[] args) { SupClass[] sca = new SupClass[10]; } }
这段代码不会输出任何结果。由于经过数组定义来引用类,不会触发此类的初始化。
场景三
public class ConstClass { public static final String HELLO = "hello"; static{ System.out.println("ConstClass init..."); } }
客户端代码
public class InitTest { public static void main(String[] args) { System.out.println(ConstClass.HELLO); } }
输出以下
hello
能够看到常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,所以不会触发定义常量的类的初始化。
本篇文章依据如下两点