JAVA虚拟机 把描述类的数据 从 class文件 加载到内存,并对数据进行校验、转换解析和初始化,最后形成可以被虚拟机直接使用的指令和数据 的这个过程 叫做虚拟机的类加载机制。
JAVA类加载的 加载、连接、初始化过程整个发生在 程序运行期间。虽然比起编译型程序多了类加载过程的开销,但是增加了程序的扩展性和灵活性。
一、一个class文件从硬盘到内存的整个生命周期
加载Loading -> [验证Verification,准备Preparation,解析Resolution] -> 初始化Initialization -> 使用Using -> 卸载Unloading
1.1 JVMS规定了6种必须立即对类进行初始化的情况
1. 遇到new、getstatic、putstatic、invokestatic这四条字节码指令时。
触发这些指令的情况有: 使用new 关键字实例化对象时。读取或设置一个类的静态字段(被final修饰、已在编译期把结果放到常量池的静态字段除外)时。调用一个类的静态方法时。
2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化过需要触发。
3. 当初始化类时,如果它的父类还没有被初始化,那么需要先触发父类的初始化。
4. jvm启动时需要用户指定一个要执行的主类(包含main方法),虚拟机会先初始化这个主类。
5. 如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic,REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发。
6. 当一个接口定义了default方法,如果这个接口的实现类想初始化,则接口要在实现类前初始化。
有且只有这6种情况会初始化类。
public class Father {
static {
System.out.println("in Father static");
}
public static int i = 10;
}
public class Son extends Father{
static {
System.out.println("in son static");
}
public static int value = 10;
}
public class T {
public static void main(String[] args) {
System.out.println(Son.value);
}
}
//in Father static
//in son static
//10
如果谁Father.i,只会打印 in Father static。 因为只直接调用了父类的静态属性,而实例化Son实,要满足 情况3 先初始化父类。
二、加载过程
2.1 加载Loading
2.3 准备
准备阶段 正式 将类中定义的变量(即静态变量,被static修饰的)分配内存并设置初始值的阶段。
概念上将这些变量的内存都在堆中的方法区中。因为每个类都在方法区中存储着字段信息、常量池和方法代码。
public static int age = 123;
准备阶段结束后age是0,因为把age赋值为123的putstatic指令要在初始化阶段执行类构造器的cinit()方法后才执行。
2.4 解析
笔记来自:
【1】深入理解JAVA虚拟机:JVM高级特性与最佳实践