首先看一下Java虚拟机与程序的生命周期的关系。在如下几种情况下,Java虚拟机将结束生命周期:
a,执行了System.exit()方法
b,程序正常执行结束
c,程序在执行过程中遇到了异常或错误
d,由于操作系统出现错误导致java虚拟机进程终止

类的加载、连接与初始化


加载:查找并加载类的二进制数据
连接:包含验证,准备,解析
验证:确保加载类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值
解析:把类中的符号引用转换成直接引用
初始化:为类的静态变量赋予正确的初始值。

类的加载、类加载时机


类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
类的加载时机是指JVM规范允许类加载器在预料某一个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError)
如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

类的验证


类被加载后,就进入连接阶段,连接就是将已经读入到内存中的类的二进制数据合并到虚拟机的运行时环境中去。然后进行验证,验证类的字节码的合法性。确保加载类的正确性

类的准备


在准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认的初始值。

类的解析


Java虚拟机会把类的二进制中的符号引用替换成直接引用,例如在Worker类中的gotoWork()方法中会引用Car类的run()方法:
1
2
3
public void gotoWork(){
car.run();//这段代码在Worker类中的二进制数据中表示为符号引用
}

在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run()方法的全面和相关描述符组成,在解析阶段,Java虚拟机会把这个
符号引用替换成一个指针,该指针指向Car类的run()方法在方法区内的内存位置,这个指针就是直接引用。

初始化


Java虚拟机执行类的初始化语句,为类的静态变量赋予正确的初始值。在程序中,静态变量的初始化有两种途径:
第一种在静态变量的声明处进行初始化。
第二种在静态代码块中进行初始化。
类初始化步骤:
A,假如这个类还没有被加载和连接,那就先进行加载和连接
B,假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类。
C,假如类中存在初始化语句,那就依次执行这些初始化语句。
当Java虚拟机初始化一个类的时候,要求它所有的父类都已经被初始化,但是这条规则并不适合接口。
A,初始化一个类时,并不会先初始化她所实现的接口
B,在初始化一个接口时,并不会先初始化它的父接口。

主动引用与被动引用


所有的Java虚拟机实现必须在每个类或接口被Java程序”首次主动使用”时才初始化他们。
主动使用(六种)
1,创建类的实例
2,访问某一个类的静态变量,或者对该静态变量赋值,此静态变量必须是定义在本来。如果子类继承并访问,并不会对子类的主动使用。因为在子类中调用静态变量实际调用的是父类的静态变量。
如果子类中定义了和父类一样的静态变量,则先初始化父类,然后再初始化子类,静态变量的值会是子类所定义的值。
3,调用类的静态方法
4,反射
5,初始化一个类的子类
6,Java虚拟机启动时被标明为启动类的类(Java Test)
除了以上六种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化。

实例1


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyTest {

public static void main(String[] args) {
Singleton singleton=Singleton.getInstance();
System.out.println("counter1 =" +singleton.counter1);
System.out.println("counter2 =" +singleton.counter2);
}
}
class Singleton{
public static Singleton singleton=new Singleton();
public static int counter1;
public static int counter2=0;

private Singleton(){
counter1++;
counter2++;
}
public static Singleton getInstance(){
return singleton;
}
}

以上的代码就可以根据上面学习来计算出:
counter1=1;
counter2=0;

实例2


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyTest {

public static void main(String[] args) {

Singleton singleton=Singleton.getInstance();
System.out.println("counter1 =" +singleton.counter1);
System.out.println("counter2 =" +singleton.counter2);
}
}
class Singleton{

public static int counter1;
public static int counter2=0;
public static Singleton singleton=new Singleton();

private Singleton(){
counter1++;
counter2++;
}
public static Singleton getInstance(){
return singleton;
}
}

以上的代码就可以根据上面学习来计算出:
counter1=1;
counter2=1;

实例3


1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyTest {
public static void main(String[] args) {
System.out.println(Test.X);
}
}
class Test{

public static final int X=6/3;

static{
System.out.println("abc");
}
}

此题目输出的结果为2。static代码块并没有执行,因为在编译期间就知道X的值,所以就不需要去加载初始化类。

实例4


1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyTest {
public static void main(String[] args) {
System.out.println(Test.X);
}
}
class Test{

public static final int X=new Random.nextInt(100);

static{
System.out.println("abc");
}
}

此题目输出的结果为2,”abc”,static代码块执行了,因为在编译期间不知道X的值,所以只有在运行期间才知道其值,就需要去加载初始化类。