虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
与那些在编译时需要进行连接工作的语言不同,在Java语言里面,类型的加载、连接和初始化过程都是在程序运行期间完成的。
从被虚拟机加载到内存开始,到卸载出内存为止,整个生命周期:
加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的开始(互相交叉的运行)
,但是解析阶段则不一定:
可以在初始化阶段之后再进行(动态绑定)
已在编译期把结果放入常量池的静态字段除外
)、putstatic(设置静态字段)或invokestatic(调用一个类的静态方法)这四条字节码指令时,如果没有进行过初始化,则初始化。(如果是接口,并不要求其父接口全部完成了初始化,只有在使用的时候才会被初始化)
public class SuperClass {
static {
System.out.println("superClass init");
}
public static int value=123;
}
public class SubClass extends SuperClass {
static {
System.out.println("subClass init");
}
}
public class ExampleTest {
public static void main(String[] args) {
System.out.println(SubClass.value);
//输出结果为
//superClass init
//123
}
}
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。
public class Example2Test {
public static void main(String[] args) {
SuperClass[] superClasses = new SuperClass[10];//没有输出
}
}
没有输出说明并没有触发xxx.xxx.SuperClass的初始化阶段(也就是没有执行()方法)。但是却触了另一个类的初始化
,这个类就是[Lxxx.xxx.SuperClass,这是由虚拟机自动生成的,继承java.lang.Object类的子类,创建动作由字节码指令newarray触发。这个类代表了SuperClass的一维数组,数组中应有的属性和方法都实现在这个类中。
public class ConstantClass {
static {
System.out.println("Constant init");
}
public static final String HELLO = "hello";
}
public class Example3Test {
public static void main(String[] args) {
System.out.println(ConstantClass.HELLO);
//输出结果为:hello
}
}
没有输出"Constant init",这是因为在编译阶段通过常量优化,将常量的值放入了Example3Test类的常量池中,以后对该常量的访问都转化为自身对常量池的引用了。实际上Example3Test的Class文件之中并没有ConstantClass类的符号引用入口,这两个类在编译成Class文件后就不存在任何联系
在加载阶段,虚拟机需要完成的三件事:
静态存储结构
转化为方法区的运行时数据结构
HotSpot中存放在方法区
),作为方法区这个类的各种数据的访问入口一部分验证点:
只有通过了这个阶段的验证后,字节流才会进入内存的方法区中进行存储,所以后面的三个验证阶段全部是基于方法区的存储结构进行的,不会直接操作字节码
一部分验证点:
在操作数栈中放置一个int类型的数据,使用时却按照long类型来加载入本地变量表
准备阶段是正式为类变量(static修饰的变量,实例变量将会在对象实例化时分配在java堆中)
分配类存并设置类变量初始值
的阶段(初始值通常
是数据类型的零值,特殊情况
是被加上final修饰的数据初始值就是指定的值,如public static final int value = 123),变量所使用的内存在方法区中进行分配。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
注意
:虚拟机对解析进行缓存,对符号引用的第一次结果进行缓存(在运行时常量池中记录直接引用,并把常量标为已解析状态),从而避免重复解析(对invokeddynamic指令,上面规则不成立,必须等到代码实际运行到这条指令)
初始化阶段是类加载过程的最后一步,根据程序员通过程序制定的主观计划去初始化类变量和其他资源(执行()方法的过程)