虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的java对象,这就是虚拟机的类加载机制。
在java中,类的加载,连接和初始化过程都是程序运行期间完成的,这样虽然会使类加载器额外造成一些开销,但是会让java程序有更高的灵活度。
JAVA动态可拓展的特性就是依赖运行期动态加载和动态连接的特点实现的。我们可以编写一个程序,等到运行时在具体指定他的实现类,这就是反射的思想。
类的生命周期
类的加载过程必须按照这种顺序按部就班的开始,而解析阶段不一定,他在某些情况可以在初始化阶段之后在开始,这是为了支持java的运行时绑定。
什么时候必须要对类进行加载初始化?
- 使用new关键字实例对象的时候,读取或设置一个类的静态字段的时候,以及调用一个类的静态方法的时候。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,首先对其进行初始化。
- 初始化一个类的时候,发现其父类还没有初始化的时候,首先要对其父类进行初始化。
- 虚拟机启动的时候,首先对Main方法入口类进行初始化。
上述几种情况称为对类进行主动引用,其他引用不会触发初始化,称为被动引用。
被动引用常见情况:
- 通过子类引用父类的静态字段,不会导致子类初始化。
- 通过数组定义来引用类,不会触发此类的初始化。
- 加载常量也不会触发类加载机制。
那么“加载”都做些什么呢?
一般都需要完成下面三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个java.lang.Class对象,作为方法区这个类的各种数据访问入口。
在加载阶段既可以使用系统提供的引导类加载器来完成,也可以由用户自定义的类加载器来完成,开发者可以通过重写loadClass()方法来控制字节流的获取方式。
加载完成后,这些字节流就会按照二进制字节流的方式存储在方法区中(1.7)
但是对于数组对象会有一些特殊:
如果是引用对象数组,那么就递归的方式调用加载过程去加载,数组会在类加载该类型的类加载器的类名称空间被标识。
如果是普通类型,数组标识与引导类加载器关联。
然后就到了验证阶段:
验证属于连接阶段,加载过程与连接过程是交替进行的。
验证的目的是为了确保Class文件的字节流中所包含的信息复合当前虚拟机的要求,并不会危害虚拟机自身的安全。
然后是准备阶段:
这一阶段是正式为类变量(static修饰的变量)分配内存并设置类变量初始值的阶段,这一部分会被分配在方法区中。
static int a =100;
这一阶段之后,a的值为0,而不是100,因为没有执行任何java方法。
再然后解析阶段:
这一过程是将常量池的符号引用替换为直接引用的过程。
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。
直接引用:直接引用可以是直接印象目标的指针,相对偏移量或是一个能简介定位到目标的句柄,当然这种实现与虚拟机有关。
最后就是初始化阶段:
这个过程是执行类构造器的<client>()方法,这个过程是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,这个顺序是由语句在源文件中出现的顺序所决定的。