我的Java类中static语句块运行了吗?

0.前言

有如下代码:

public class Test {

	public static void main(String[] args) {
		System.out.println(MyStatic.AAA);
		
		System.out.println(MyStatic.BBB);
	}

}

public class MyStatic {
	public static final String AAA = "aaa";
	
	public static String BBB = "bbb";
	
	static {
		System.out.println("my static");
	}
}

打印的结果是什么呢?

结果:

aaa
my static
bbb

为什么在打印bbb之前运行了MyStatic类的static语句呢?

下面解释下。


参考资料:《深入理解Java虚拟机:JVM高级特性与最佳实践 第二版》

本文关键内容都摘自此书第七章(将涉及到java字节码的内容使用删除线标记,便于不了解java字节码时阅读)


先贴一张书里的图,是Java文件加载到内存中使用的一个流程。

其中加载(Loading)过程是指java从文件读取成二进流并建立一些类相关内容。连接(Linking)过程是对这个二进制内容分析相关过程。其中加载(Loading)初始化(Initialization)是可以开做是我们在调用类的变量和方法之前对类变量的初始化的前奏步骤。

1.类“加载”的时机

Java虚拟机规范没有强制要求。可以确定的是这个动作是由类加载器(ClassLoader)完成的,是在虚拟机的外部。我们可以用自己的代码来实现灵活加载,这是Java很特别的一点。

这个阶段主要做了以下事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流。 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口


参考资料(ClassLoader):http://blog.itpub.net/31561269/viewspace-2222522/


Java 中,通过的类加载器这个类本身来唯一标识一个类。相同的类文件,不同类加载器,生成的Class类不同。

2.类“初始化”的时机

到了初始化阶段,代表着虚拟机开始真正执行java程序代码

初始化阶段执行类变量的赋值动作和静态语句块 (static{}块),执行顺序由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。

初始化阶段是执行类构造器<clinit>方法的过程。

<clinit>方法可以在生成class文件中看到。

<clinit>方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块 (static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。

当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。

有且只有5种情况必须立即对类进行“初始化”: 

1⃣️ 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

被final修饰、已在编译期把结果放入常量池的静态字段除外。

在编译阶段,通过常量传播优化,已经将此常量值存储到了调用类的常量池中。

2⃣️ 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化, 则需要先触发其初始化。

3⃣️ 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。(接口不同)

4⃣️ 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

5⃣️ 当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化

动态语言、动态语言支持。

参考文章:https://blog.csdn.net/xtayfjpk/article/details/42043977

书中还列举了两个不会初始化类的特殊场景:

1.通过子类引用父类的静态字段,只会出发子类的初始化不会触发父类初始化。2.定义某个类的数组,不会初始化这个类,而会初始化一个由虚拟机生成的代表数组的类。

3.结束

从上面的了解到。MyStatic类的AAA常量是被final修饰,访问这个变量不会引起MyStatic类的初始化;而BBB常量未被fianl修饰,引起MyStatic初始化。

猜你喜欢

转载自blog.csdn.net/u012218652/article/details/90298061