首次主动使用 时才初始化 静态变量 ,或者对该 静态变量 赋值
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 ,所以就会导致这种输出结果. 这个例子在实际工作中基本不会这样写,但是能很好的帮助理解类的准备阶段和初始化阶段做分别做的事情