首次主动使用
时才初始化 静态变量
,或者对该 静态变量
赋值
getstatic
, putstatic
这两个指令 除了主动使用的7种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的 初始化
,但是依然会对类进行 加载
和 连接
public class Test02 { public static void main(String[] args) { //情况1.调用子类的str, //输出 parent static block // hello jvm // System.out.println(Child.str); //情况2.调用子类的str2 //输出 parent static block // child static block // hello jvm2 // System.out.println(Child.str2); } } class Parent{ public static String str = "hello jvm"; static { System.out.println("parent static block"); } } class Child extends Parent{ public static String str2 = "hello jvm2"; static { System.out.println("child static block"); } } 复制代码
主动使用
-XX:+TraceClassLoading
可以观察到即使调用Child.str,Child没有被初始化,但是依然会被jvm加载到内存中
-XX:
是开头
代码举例
public class Test03 { public static void main(String[] args) { //情况1.没有使用final // System.out.println(Parent2.str); //情况2,使用了final System.out.println(Parent2.str2); } } class Parent2 { /** * 注意是final */ public static String str = "hello jvm"; public static final String str2 = "hello jvm"; static { System.out.println("Parent2 static block"); } } 复制代码
如图中,打印的输出结果是
hello jvm 复制代码
final修饰的常量,在编译阶段,就会被放在调用这个常量的方法的所在的类的常量池,本质上,调用类并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
.通过javap -c 反编译Test03的class文件
javap -c Test03 复制代码
Compiled from "Test03.java" public class com.r09er.jvm.classloader.Test03 { public com.r09er.jvm.classloader.Test03(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; // 已经将final的静态变量直接定义为常量 3: ldc #4 // String hello jvm 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return 复制代码
助记符:
ldc,表示将int,float或者是String类型的常量值从常量池中推送至栈顶
public class Test04 { public static void main(String[] args) { System.out.println(Parent4.str); } } class Parent4 { public static final String str = UUID.randomUUID().toString(); static { System.out.println("parent4 static block"); } } 复制代码
这个例子中,虽然 str
也是静态常量,但是在编译期str的值并不能确定,这个值就不会被放到常量池中,所以在程序运行时,会导致主动使用这个常量所在的类,导致这个类的初始化
对于数组实例来说,其类型是JVM在运行期动态生成的,表示为 [Lcom.xxx.xxx
这种形式,动态生成的类型,其父类型就是Object
对于数组来说,JavaDoc经常将构成数组的元素称为Component,实际上就是将数组降低一个维度之后的类型
代码示例
public class Test05 { public static void main(String[] args) { Parent5[] parent5Arr = new Parent5[1]; System.out.println(parent5Arr.getClass()); Parent5[][] parent5Arr2 = new Parent5[1][1]; System.out.println(parent5Arr2.getClass()); System.out.println(parent5Arr.getClass().getSuperclass()); System.out.println(parent5Arr2.getClass().getSuperclass()); System.out.println("==="); int[] intArr = new int[1]; System.out.println(intArr.getClass()); System.out.println(intArr.getClass().getSuperclass()); } } class Parent5{ static { System.out.println("Parent5 static block"); } } 复制代码
输出
class [Lcom.r09er.jvm.classloader.Parent5; class [[Lcom.r09er.jvm.classloader.Parent5; class java.lang.Object class java.lang.Object === class [I class java.lang.Object 复制代码
在类的初始化阶段,会从从上至下初始化类的静态属性,所以会有一个顺序性问题.这个问题可能会导致意外结果 如下有一个例子
public class Test07 { 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=1; private static Singleton singleton = new Singleton(); private Singleton(){ //已经被初始化了 counter1++; //由于counter还在后面,还未进行初始化,所以用的是默认值0 counter2++; System.out.println("construct counter1=="+counter1); System.out.println("construct counter2=="+counter2); } public static int counter2 = 0; public static Singleton getInstance(){ return singleton; } } 复制代码
输出
construct counter1==2 construct counter2==1 counter1==2 counter2==0 复制代码
在这个例子中,在初始化阶段,执行到 private static Singleton singleton = new Singleton();
时候,会执行私有构造,在私有构造中,由于 counter1
已经完成了初始化,即已经被赋值为1,所以count++后输出结果为2,然而 counter2
还未执行初始化,所以使用的还是在准备阶段的默认值 0
,所以就会导致这种输出结果. 这个例子在实际工作中基本不会这样写,但是能很好的帮助理解类的准备阶段和初始化阶段做分别做的事情