在java源文件(.java)被编译成字节码(.class)文件后,启用命令java Demo,就会调用Demo类的main()方法来启动java虚拟机(jvm)。当JVM加载到内存后,调用Demo的main()方法开始它的工作。JVM将按特定顺序做三件事:加载、链接和初始化。
1. 加载
JVM将java类的二进制形式加载到内存中,并将他缓存在内存中,以便后面使用,如果没有找到指定的类就会抛出异常classNotFound,进程在这里结束。没有错误就继续在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区域数据的访问入口。
2.链接
这个阶段做三件事:验证、准备和解析(可选)。验证是JVM根据java语言和JVM的语义要求检查这个二进制形式。例如,如果篡改经过编译后的类文件,那么这个类文件可能就不能使用了。准备是指准备要执行的指定的类,准备阶段为变量分配内存并设置静态变量的初始化。在这个阶段分配的仅为类的变量(static修饰的变量),而不包括类的实例变量。对非final的变量,JVM会将其设置成“零值”,而不是其赋值语句的值:如
public static int num = 8;
那么在这个阶段,num的值为0,而不是8。 final修饰的类变量将会赋值成真实的值。
解析是检查指定的类是否引用了其他的类/接口,是否能找到和加载其他的类/接口。这些检查将针对被引用的类/接口递归进行,JVM的实施也可以在后面阶段执行解析,即正在执行的代码真正要使用被引用的类/接口的时候。 例如:指定的类包含下面代码:
MathUtil.add(2,1);
那么在调用静态add方法之前,JVM将加载、链接、初始化MathUtil类。
3.初始化
最后一步中,JVM用赋值或者缺省值将静态变量初始化,初始化发生在执行main方法之前。在指定的类初始化前,会先初始化它的父类,此外,在初始化父类时,父类的父类也要这样初始化。这个过程是递归进行的。
public class StaticInitTest {
public static int a=5;
public static int b=a*2;
static{
System.out.println("Static");
System.out.println(b);
}
public static void main(String[] args) {
System.out.println("main method");
}
}
运行后输出:
Static
10 //在执行main方法前先将静态变量初始化,并执行静态初始化程序
main method //之后执行main方法
最后使用和卸载就不说了,使用过程就是根据程序定义的行为执行,卸载由GC完成。