JVM(二)類的主動使用與被動使用

對類的使用方式

  • 主動使用
  • 被動使用

全部Java虛擬機實現必須在每一個類或接口被Java程序首次主動使用時才初始化java

  • 主動使用才進行初始化
  • 第一次主動使用才進行初始化,以後就再也不初始化

只有當程序訪問的靜態變量或靜態方法確實在當前類或當前接口中定義時,纔可認爲是對類或接口的主動使用shell

主動使用

  • 建立類的實例
    • new Object()
  • 訪問某個類或接口的靜態變量,或者對該靜態變量賦值
    • 助記符,經過javap 反編譯後能夠獲得getstatic,putstatic這兩個指令
  • 調用類的靜態方法
    • invokestatic
  • 反射
    • Class.forName("com.xxx.xxx")
  • 初始化一個類的子類
  • Java虛擬機啓動時被標明爲啓動類的類
    • 包含main方法的類
  • JDK7開始提供的動態語言支持,java.lang.invoke.MethodHandle

被動使用

除了主動使用的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");
    }
}

複製代碼

結果總結

  • 對於靜態字段來講,只有直接定義了該字段的類纔會被初始化
  • 雖然調用了Child.str.可是因爲對Child沒有進行主動使用,因此不會被初始化,因此不會輸出靜態代碼塊中的內容
  • 由於初始化了子類,因此也會初始化父類,而且父類先初始化完畢
  • 每一個類只會初始化一次

添加JVM參數-XX:+TraceClassLoading

image-20200311155635549

能夠觀察到即便調用Child.str,Child沒有被初始化,可是依然會被jvm加載到內存中bash

JVM參數規律

-XX:是開頭dom

  • -XX:+option,開啓option選項
  • -XX:-option,關閉option選項
  • -XX:option=value,標識將option的值設置爲value

final修飾的變量

代碼舉例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

複製代碼

ldc

助記符: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,因此就會致使這種輸出結果. 這個例子在實際工做中基本不會這樣寫,可是能很好的幫助理解類的準備階段和初始化階段作分別作的事情

相關文章
相關標籤/搜索