程序运行时,类的生命周期有加载、验证、准备、解析、初始化五个过程。
(1)、准备
JVM为类变量分配内存以及初始化
java中的变量有2种类型,分别为类变量(被static修饰的变量)和类成员变量(除了类变量的其它变量)
在准备阶段,JVM只会为类变量分配内存, 而不会为类成员变量分配内存,到初始化阶段才会为类成员变量分配内存
(1.1)、初始化的类型
在准备阶段,jvm会为类变量分配内存,并为其初始化,但是这里的初始化和我们平时理解的初始化不太一样,这里的初始化是指为变量赋其类型在java中的零值,而不是用户代码里面的初始值,但是如果一个变量被static final修饰的话,则会被赋予用户所希望的值,示例:
// 在准备阶段,jvm会为其赋值为0,而不是1
public static int test = 1;
// 在准备阶段,jvm会为其赋值1
public static final int test2 = 1;
为什么同样是被static修饰,但是结果不一样,我们可以这么理解:在java中,被final修饰代表不可变,如果被static final修饰,在准备阶段被赋值为0,这样和java里面final的约定不一致,但是如果是赋值为1,那么就合符情理。没有被final修饰的变量,其可能在初始化阶段或者运行阶段发生一系列变化,所以就没有必要在准备阶段赋予用户所要的值。
(2)、初始化
初始化阶段,用户定义的java代码才真正开始执行,jvm会根据语句的执行顺序对类对象初始化
在这个阶段,主要是为静态变量赋值为程序指定的初始值,执行静态代码块。
注意:
1、当java虚拟机初始化一个类时,要求他所有父类都已经被初始化,但是这条规则并不适合接口。在初始化一个类或者接口时,并不会先初始化他所实现的接口。
2、只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用,如果静态方法或变量在parent中定义,从子类进行调用,则不会初始化子类。
(3)、实战分析
public class testStatic {
public static void main(String args[]){
Singleton singleton=Singleton.getInstance();
System.out.println(singleton.count1);
System.out.println(singleton.count2);
}
}
class Singleton{
private static Singleton singleton=new Singleton();
public static int count1;
public static int count2=0;
private Singleton(){
count1++;
count2++;
}
public static Singleton getInstance(){
return singleton;
}
}
输出结果: 1 0
分析:1、准备阶段:singleton变量默认值被赋值为null,count1 和count2 的类型为int,默认值为0,同时为三个类变量分配内存。2、初始化阶段:singleton变量被 new Singleton();此时执行Singleton的构造方法,count1和count2分别+1,此时为count1=1,count2=1。程序接着执行,由于count1 未被程序员指定初始值,其值不变,count2的初始值又被设置为0,所以最终输出 1 0
(4)、什么时候需要对类进行初始化?
1、使用new该类实例化对象的时候;
2、读取或设置类静态字段的时候(除final修饰的字段);
3、调用类静态方法的时候
4、使用反射Class.forName("xxxx")对类进行反射调用的时候,该类需要初始化;
5、初始化一个类的时候,有父类,先初始化父类。子类引用父类静态字段,只会引发父类初始化;
6、被标明为启动类的类(即包含main()方法的类)要初始化;
public class test { //1.第一步,准备加载类
public static void main(String[] args) {
new test(); //4.第四步,new一个类,但在new之前要处理匿名代码块
}
static int num = 4; //2.第二步,静态变量和静态代码块的加载顺序由编写先后决定
{
num += 3;
System.out.println("b"); //5.第五步,按照顺序加载匿名代码块,代码块中有打印
}
int a = 5; //6.第六步,按照顺序加载变量
{ // 成员变量第三个
System.out.println("c"); //7.第七步,按照顺序打印c
}
test() { // 类的构造函数,第四个加载
System.out.println("d"); //8.第八步,最后加载构造函数,完成对象的建立
}
static { // 3.第三步,静态块,然后执行静态代码块,因有输出,故打印a
System.out.println("a");
}
static void run() // 静态方法,调用的时候才加载// 注意看,e没有加载
{
System.out.println("e");
}
}
本文介绍了Java程序运行时类的生命周期,包括加载、验证、准备、解析、初始化五个过程。重点阐述了准备阶段为类变量分配内存及初始化,初始化阶段执行用户代码为静态变量赋值和执行静态代码块。还说明了类初始化的规则及何时需要对类进行初始化。
16万+

被折叠的 条评论
为什么被折叠?



