什么时候要进行类加载在虚拟机规范中并没有强制规定,靠虚拟机的具体实现来进行自由把握
类的加载就是将class文件加载到内存中,并建立class对象
与那些在编译时需要进行连接工作的语言不同,在Java语言里面,类型的加载、连接和初始化过程
都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性,Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。
java程序,编译的时候只会编译成class文件,类加载是运行期间的
类的生命周期:
1.加载,验证,准备,解析,初始化,使用,卸载,
2.其中五个顺序是确定的,解析的顺序不确定,它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)
3.类的加载过程必须按照这种顺序按部就班地开始,不是强调完成,强调这点是因为这些阶段通常都是互相交叉地混合式进行的,通常会在一个阶段执行的过程中调用、激活另外一个阶段
4.什么时候开始类加载,开始类加载的第一个阶段,没有强制约束,这是交给虚拟机自由把握的,虚拟机可能会预期执行某个类的类加载,但是如果这个类加载的过程中出了什么错,它不会立刻报错,他会在的第一次使用的时候报错,如果一直都没有用到这个类就不会报错
5.但是有五中情况必须立刻对类进行初始化(前面的步骤要在之前开始)
第一种:new一个实例对象的时候,或者调用一个类静态字段的时候(被final修饰,已在编译器把结果放入常量池的静态字段除外),以及调用一个静态方法的时候
第二种:使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
第三种:对一个子类初始化的时候,要先对其父类进行初始化
第四种:虚拟机启动的时候,会先初始化main方法所在的类,也就是主类
类加载的过程:
1.加载:
在内存中生成一个代表在这个类的java.lang.Class对象,作为方法区这个类的各个数据的访问入口,
2.验证:
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
3.准备:
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下,首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,初始化的时候才会执行程序员自己写的代码
public static int value=123;准备阶段是0
那变量value在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何Java方法。
在“通常情况”下初始值是零值,那相对的会有一些“特殊情况”:如果类字段的字段属性
表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,假设上面类变量value的定义变为:
public static final int value=123;准备阶段是123
4.解析:
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,虚拟机规范之中并未规定解析阶段发生的具体时间,所以虚拟机实现可以根据需要来判断到底是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用前才去解析它。
5.初始化:(类加载过程的最后一步)
在编译成class文件的时候,会自动产生两个方法,一个是类的初始化方法clinit(包括静态变量语句的执行和静态代码块的执行),一个是实例的初始化方法init
1.到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。初始化阶段是执行类构造器<clinit>()方法的过程
2.<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问,如代码清单7-5中的例子所示。
public class Test{
static{
i=0;//给变量赋值可以正常编译通过
System.out.print(i);//这句编译器会提示"非法向前引用"
}
static int i=1;
3.<clinit>()方法与类的构造函数(或者说实例构造器<init>()方法)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此在虚拟机中第一个被执行的<clinit>()方法的类肯java.lang.Object。
如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。
4.虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕
参考深入理解java虚拟机