Java类加载过程&&静态代码块的初始化过程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tianjindong0804/article/details/84797451

问题的引入       

还是老规矩,先说说自己遇到的问题。

最近看到了一个比较有意思的Java程序,初次看到这段程序执行的结果还是挺让我意外的,话不多说先上程序,大家也可以揣摩一下(大神自行略过......)

class Singleton{
	private static Singleton singleton=new Singleton();
	public static int count1=0;
	public static int count2;
	
	private Singleton(){
		count1++;
		count2++;
	}
	public static Singleton getInstance(){
		return singleton;
	}
}

public class MyTest {
	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		System.out.println("count1:"+instance.count1);
		System.out.println("count2:"+instance.count2);
	}
}

      看到这里我想大家已经有了这个程序的结果了把。不知道大家的结果是否正确:

       如果你对这个结果很意外那请你接着往下看吧,嘻嘻。如果你答对了,如果你答对了也建议你看完这篇博文,或许你可以收获一点东西,让你的思路更加清晰。

知识点回顾

(敲黑板了,敲黑板了)
  首先我们需要明确的就是,在Java中静态变量如果在定义时赋初值实际上就是在在静态代码块中赋初值,(这一过程我们可以通过反编译工具查看细节,这里不做赘述);同样的非静态成员的如果在定义时赋初值,实际上就是在构造器的第一行初始化的改变量。

也就是说上面的代码在编译后,会自动将代代码编程这样

class Singleton{
	private static Singleton singleton;
	public static int count1=0;
	public static int count2;
	static{
		singleton=new Singleton();
		count1=0;
	}
	
	private Singleton(){
		count1++;
		count2++;
	}
	public static Singleton getInstance(){
		return singleton;
	}
}

public class MyTest {
	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		System.out.println("count1:"+instance.count1);
		System.out.println("count2:"+instance.count2);
	}
}

     上面的这个可以通过反编译工具查看,但是部分反编译工具反编译后的效果仍然是我第一次写的那样,但这些都不是重点,我们只需要知道实际上,在定义静态变量时附的初始值实际上在编译后会移到静态代码块中进行,而静态代码块的作用域构造器的优点相似,都是用于初始化,但是不同的是构造器是用于初始化非静态变量的,而静态代码块是用于初始化静态变量的。

上面就是通过XJad反编译工具打开的效果,注意最后的静态代码块(不同的反编译工具,反编译后的代码会有差异)

回顾这一个知识点,就是明确类的静态变量的初始化,是在静态代码块中进行的。

回到正题

      我们都知道,一个类在被首次主动使用之前会被类加载进内存并初始化,而在初始化之前,JVM到底做了些什么呢?这里我们来简单的说一下。

类加载的步骤:

     第一步:类的加载

     第二步:连接

     第三步:类的初始化

类的加载

  类的加载指的是将.class文件加载进内存

连接

 连接就是将已经读入到内存的类的二进制数据合并到虚拟机运行时环境中去。

      连接也分为三步:

第一步:验证

            确保加载的字节码文件的正确性

第二步:准备

            为类的静态变量分配内存,并将初始化默认值。short,int,long的默认值为0,boolean的默认值为false,引用类型的默认                           值为null.

第三步:解析

            把类中的符号引用转换为直接引用。

类的初始化

        为类的静态变量赋予正确的初始值,实际上就是执行静态代码块中的内容。

        由上面的的描述我们就知道,一个类加载进内存会先为静态变量分配内存,并指定初始值。最后一步才执行初始化。

代码分析

         我们在执行Singleton instance = Singleton.getInstance();时,由于此时Singleton还没被加载进虚拟机,所以虚拟机会自动的加载它,在连接阶段会为singleton,count1,count2分配内存,并赋上初始值。在连接阶段完成后会进行类的初始化,这一过程实际上就是执行类的静态代码块,首先会先执行singleton=new Singleton();,执行完毕后,count1和count2都为1。然后执行count1=0,此时count1等于0,count2等于1,这也就是最后输出的结果。

思考:如果把代码改成这样会输出什么?

class Singleton{
	public static int count1=0;
	private static Singleton singleton=new Singleton();
	public static int count2=0;
	
	private Singleton(){
		count1++;
		count2++;
	}
	public static Singleton getInstance(){
		return singleton;
	}
}

public class MyTest {
	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		System.out.println("count1:"+instance.count1);
		System.out.println("count2:"+instance.count2);
	}
}

答案:count1=1 count2=0

总结

  一个类被加载进内存分为类的加载、连接、初始化三个阶段。

    一个类的静态变量的初始值是在类加载过程中的连接阶段中完成的,而类的初始化过程就是在类的初始化阶段中完成的,这个阶段中会执行静态代码块,为静态变量赋予正确的值。(程序员想要的指定的值)

猜你喜欢

转载自blog.csdn.net/tianjindong0804/article/details/84797451