在讲JVM最开始,我们先以一个简单的Java程序的运行开始讲,JAVA程序的运行原理。下面我先把我们实例程序列出来,我们该实例程序一共有两个java文件:Hello.java和Person.java: Hello.java文件:
public class Hello {
public static void main(String[] args) {
Person p = new Person();
}
}
Person.java文件
public class Person {
private String name;
}
首先在IDE中编写java源文件,文件名以.java结束。编写完源文件,然后使用javac命令把源文件编译成以.class结尾的二进制文件或者打包为jar文件。 当我们需要去执行我们编写的java程序的时候,如果是直接编译后的.class文件,那么使用:
java com.xx.Hello.class
java *.class命令去运行包含了main方法的那个.class文件。如果我们是以jar的方式进行运行java程序,那么执行 :
java -jar *.jar com.xx.Hello
起中 Hello是这个jar中包含了main方法的类的名称。 无论是那种方式,当我们执行的时候,就会启动jvm虚拟机去加载所要执行的Hello.class文件到虚拟机中。然后在jvm中有字节码执行引擎负责去执行Hello.class中的main方法,在main方法中使用到了Person类,此时jvm又会去加载Person。也就是说jvm用到哪个类,然后就去加载哪个类。下面用图来描述一下java程序的执行过程:

类的加载过程
通过上面的结束,基本了解了java程序的一个大致的执行过程,在上面描述的加载过程中,其中一个最重要的环节是类的加载。下面接受一下类是如何加载的。 在Java类被加载到虚拟机到从内存中卸载,整个生命周期包括以下部分: 加载,连接,初始化,使用,卸载 在java中的所有的类型都是在运行的过程中进行加载,连接和初始化。 类的加载过程分为三个部分: 加载:把class文件加载到内存中 连接:分为三个部分:验证,准备,解析。 初始化:类加载最后一步,对类中的变量进行赋值,在代码层面是就是执行用户在类中的定义的赋值语句。
加载
类的加载,包括从本地中读取.class文件,也可以从网络中读取二进制字节流信息。
连接
其中连接又分为三个部分: 验证: 主要是包括文件格式验证,元数据验证,字节码验证 准备: 准备阶段是为类分配内存空间。 解析: 将虚拟机常量池内的符合引用替换为直接引用的过程,这部分比较复杂以后会详细说。 ###初始化 类的初始化虚拟机规范规定:只有在5种情况下才会发生: 1.执行new 对象操作、读取活设置一个类的静态字段(不包括用final修饰过的,因为final修饰的属性已经在编译器被放到常量池中)、调用一个类的静态方法。 2.使用reflect包的方法对类进行反射调用的时候。 3.当初始化一个类的时候,如果父类没有初始化,先完成对父类的初始化。 4.当JVM启动的时候,需要指定一个执行main方法的主类,那么虚拟机会首先初始化这个主类。 5.JDK 7 中使用动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例正好是对 REF_getStatic, REF_putStatic, REF_invokeStatic 进行方法句柄解析的结果时。
1.对于接口的初始化,对于子接口与父接口的规则与java类不一样,子接口的初始化并不要求父接口全部都完成了初始化,只有在真正使用到父接口的时候,才会对父接口进行初始化。 2.如果接口中定义了默认实现方法,那么当实现这个接口的类初始化的时候,也会除非这个接口的初始化。
以上五点被称为主动使用,只有主动使用的时候,类才会被初始化。而被动使用是不会引起类的初始化的。类的被动使用又主要包括下面三种情况:
- 通过子类引用父类的静态属性,父类初始化,但是子类不会被初始化
- 定义某个类的数组,该类不会初始化
- 使用类修饰的final的属性,也不会引起类初始化。