Java中有一个虚拟机的概念,就是在机器和编译程序之间多了一层抽象的虚拟的机器,这个虚拟机在各个平台上都提供给编译程序一个共同的接口(这样就可以跨平台了吧?),编译程序只需要面向虚拟机,生成虚拟机能理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码。这种让虚拟机理解的代码叫字节码。
JVM就是Java虚拟机的英文缩写,全名是Java VirtualMachine,它是怎么工作的呢?我们先写一串代码,然后介绍这个代码的运行过程。
public class Main {
private static int size=1;
public static void main(String args[]) {
User u = new User();
u.setName("Realsky");
u.setPwd("110");
String name = u.getName();
String pwd = u.getPwd();
u = null;
}
}
class User {
private String name;
private String pwd;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
装载
JVM装载指定的class文件,最终形成这个class类的实例对象。
JVM使用类装载器定位到相应的class文件,然后读取这个class文件(一个线性二进制工作流),把它传给JVM。然后JVM提取其中的类型信息,比如类的类名,方法名,变量名,修饰符,方法的返回类型等等。还要初始化常量池,里面保存了各种类型的所有常量。之后将这些东西放到一个叫方法区的地方,形成class类的实例,这个实例存放在内存的堆区,它成为java程序和内部数据结构之间的接口,程序要访问该类型的信息,就调用该类型对应的class实例对象的方法。
比如:装载Main类,读取Main类的class文件,生成对应的java.lang.Class类的实例,读取其中的类型信息,比如修饰符private,public,static,变量size,name,pwd,User共同构成了这个类的常量池,将这些信息保存在方法区。
连接
验证类,保证这个类是没有错误的。
确定类中类型符合Java语义,比如final类不能有子类,final方法不能被覆盖,等等。之后为类变量分配内存,设置默认值。最终在类型的常量类中寻找类、接口、字段和方法的复合引用替换成直接引用的过程。
比如:连接Main类,JVM为size分配内存,并设默认值0,找到常量池中User类的引用,如果User类还没有装载就装载并连接这个类,然后将常量池中对User类的引用替换为直接引用,这个时候User类还没有初始化,因为还没有用到它。
初始化
初始化一些静态变量,让这个类可以跑起来。
可能会调用()方法(这个方法只能由JVM来调用)来初始化该类的静态变量,调用这个方法之前,要确认超类的()方法已经调用完毕。
比如:初始化Main类,JVM将Main类的静态变量赋值为1.
使用
User u = new User();
创建一个User类实例,实际上是通过这个类的class实例实例化的,方法:
User u=(User)Class.forName("User").newInstance();
u.setName("RealSky"); u.setPwd("110");
调用类的方法, 为该类的变量复制。JVM的调用过程是,通过方法区找到这个方法,利用class实例的如下方法调用:
Class.forName("User").getMethod("setName").invoke(u,"RealSky");
String name = u.getName(); String pwd = u.getPwd();
和上一步类似,区别就是这个赋值给了其他变量,这个变量和实例对象一样,保存在堆区。而我们也发现了,class实例的作用就是起到一个中间作用,将程序中的调用放映到堆区上数据的变化。
u = null;
触发JVM的垃圾回收机制,使用完这个命令,User实例就没有被引用了,JVM就会回收这个实例。