全部Java虛擬機實現必須在每一個類或接口被Java程序首次主動使用
時才初始化java
只有當程序訪問的靜態變量或靜態方法確實在當前類或當前接口中定義時,纔可認爲是對類或接口的主動使用shell
靜態變量
,或者對該靜態變量
賦值
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加載到內存中bash
-XX:
是開頭dom
代碼舉例jvm
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");
}
}
複製代碼
如圖中,打印的輸出結果是ui
hello jvm
複製代碼
final修飾的常量,在編譯階段,就會被放在調用這個常量的方法的所在的類的常量池,本質上,調用類並無直接引用到定義常量的類,所以不會觸發定義常量的類的初始化spa
.經過javap -c 反編譯Test03的class文件code
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
複製代碼
助記符:cdn
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
,因此就會致使這種輸出結果. 這個例子在實際工做中基本不會這樣寫,可是能很好的幫助理解類的準備階段和初始化階段作分別作的事情