在讲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执行过程
1.执行java命令启动jvm,通过类加载器Hello.class加载到jvm中
2.jvm中的字节码执行引擎执行Hello.class中入口方法main方法
3.由于main使用了Person类,然后jvm类加载器再去加载Person类。
4.最后字节码执行引擎执行Person.class。
##类的加载过程
通过上面的结束,基本了解了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的属性,也不会引起类初始化。