Java的执行过程也就是JVM从启动到退出的过程。JVM的运行是一个进程单元,可以用jps工具列举出正在运行的JVM 进程。在一个JVM进程中可以运行多个线程。
1. JVM 启动
当用java工具运行一个编译好的class文件的时候,比如下面的命令,我们就通过调用Test的main函数启动了一个JVM进程。并且传给main函数一个字符串数组{"reboot", "Bob", "Dot", "Enzo"}
- java Test reboot Bob Dot Enzo
java Test reboot Bob Dot Enzo
2. 加载类Test
如果JVM发现Test类还没有被加载,就会调用class loader去加载这个类的class 文件。
加载类,就是说ClassLoader 利用它的defineClass 方法来从class文件构建Class对象。
ClassLoader 应该有如下的特征:
1. 对于同一个类名,ClassLoader 多次load得到的类对象应该是一样的。
2. 如果ClassLoader L1 委托ClassLoader L2去load类C,对于任意类型T,如果T是C的直接父类或接口,或者是C的属性的类型,或者是C的方法的参数的类型或者返回值的类型,那么L1和L2 load出来的T的类的对象应该是一样的。
load完成之后,就可以连接这些类型,让他们进入可执行状态。
3. 连接 Test: 验证 Verify, 准备 Prepare, 解析 (Optionally) Resolve
在执行main之前要先初始化这个类,在初始化之前还要先连接,连接就是要验证,准备和解析。
验证就是检查语法和符号表,如果失败会报错。
准备就是要准备内存,主要为静态属性以及jvm内部需要的数据结构。这时只会将静态属性设置为默认值,但是静态代码块的执行不在这里,而是在初始化阶段。
解析就是检查这个Test对其他的对象或接口的引用,看看是不是那些引用的类和接口也被load进来了。没有的话就要load。解析过程是可选的,是因为你可以选择在load Test的时候load所有Test应用的类和接口,也可以在运行时真正用到的时候再load,也就是lazy load。
4.初始化 initialization
在main执行之前,必须先对类进行初始化。初始化类的变量,还有静态代码块。初始化的时候还要先初始化它的父类。每个类都有一个隐含的父类Object。
初始化过程发生在:
1. T是一个类,当T的一个实例创建的时候,也就是T t = new T();
2. T的一个静态方法被调用的时候,也就是 T.staticField();
3. T的静态属性被赋值的时候,T.staticField = o;
4. T的一个静态属性被使用的时候,也就是 Object o = T.staticField; 但是它不是常量。
5. T is a top level class , and an assert statement lexically nested
within T is executed. (不懂,求解)
- class Super {
- static { System.out.print("Super "); }
- }
- class One {
- static { System.out.print("One "); }
- }
- class Two extends Super {
- static { System.out.print("Two "); }
- }
- class Test {
- public static void main(String[] args) {
- One o = null;
- Two t = new Two();
- System.out.println((Object)o == (Object)t);
- }
- }
class Super {
static { System.out.print("Super "); }
}
class One {
static { System.out.print("One "); }
}
class Two extends Super {
static { System.out.print("Two "); }
}
class Test {
public static void main(String[] args) {
One o = null;
Two t = new Two();
System.out.println((Object)o == (Object)t);
}
}
输出:
- Super Two false
Super Two false
One没有被初始化,因为没有被调用过,Super在Two之前初始化。
- class Super {
- static int taxi = 1729;
- }
- class Sub extends Super {
- static { System.out.print("Sub "); }
- }
- class Test {
- public static void main(String[] args) {
- System.out.println(Sub.taxi);
- }
- }
class Super {
static int taxi = 1729;
}
class Sub extends Super {
static { System.out.print("Sub "); }
}
class Test {
public static void main(String[] args) {
System.out.println(Sub.taxi);
}
}
输出: 1729
Sub没有被初始化,因为taxi实际上上Super的属性。Sub没有静态属性被使用,所以不被初始化。所以不要在子类里面初始化父类的静态变量。
因为Java程序是多线程的,所以初始化过程需要同步synchronize,这是由jvm负责的。
Class对象有可能处在下面的状态之下:
1. 验证和准备好了,但还没有初始化
2. 正在被某个线程初始化中
3. 初始化好了,可以运行了
4. 错误状态,初始化失败
5. 调用 Test.main
初始化好之后,就开始调用Test.main。这个main方法必须得是public, static, and void.
- public static void main(String[] args)
- public static void main(String... args)
public static void main(String[] args)
public static void main(String... args)
6. 卸载类对象
这是一个优化过程,是为了释放内存的,但是JVM说明书并不严格定义这一行为,这取决于具体实现的优化策略。
7. 程序退出
当下面的条件之一发生的时候:
1. 所有的非daemon线程都终止了
2. 某个线程调用了类Runtime或者System的exit方法