一道有趣的類加載面試題

  • 2019 年 10 月 29 日
  • 筆記

題目

運行如下代碼的Test1與Test2分別輸出什麼結果

  public class Parent {      static {          System.out.println("Parent static invoke");      }      public static final String FINAL_STR="FINAL_STR";      public static final Test FINAL_OBJECT=new Test();      public Parent(){          System.out.println("Parent init");      }  }  public class Child extends Parent {      static {          System.out.println("Child static invoke");      }      public Child(){          System.out.println("child init");      }  }  public class Test {      public static void main(String[] args) {          System.out.println(Child.FINAL_STR);      }  }  public class Test2 {      public static void main(String[] args) {          System.out.println(Child.FINAL_OBJECT);      }  }  

結果:

運行Test1結果

FINAL_STR

運行Test2結果

Parent static invoke  cn.lonecloud.Test@5e2de80c

解析:

Test1結果解析:

  1. 由於在mian方法中打印語句調用的是Child.FINAL_STR變量。
  2. 從Child的類中可以得知,FINAL_STR為final並且為static變量,其在調用static final變量的時候不會觸發類的初始化操作。所以結果如上

Test2結果解析:

  1. 由於Test2中引用的對象為父類Parent的靜態變量,由於並不是常量池中的對象,所以,會觸發Parent的初始化操作。

深究:

Test1位元組碼:

Compiled from "Test.java"  public class cn.lonecloud.Test {    public cn.lonecloud.Test();      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;         3: ldc           #4                  // String FINAL_STR         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V         8: return  }  
  1. ldc #4 // String FINAL_STR其為獲取靜態化變量方法,其為將常量壓入棧中,由於靜態變量在JVM中存在常量池的概念,會將字符串進行優化,所以並不會觸發類初始化

Test2位元組碼:

Compiled from "Test.java"  public class cn.lonecloud.Test {    public cn.lonecloud.Test();      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;         3: getstatic     #3                  // Field cn/lonecloud/Child.FINAL_OBJECT:Lcn/lonecloud/Test;         6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V         9: return  }

由於需要使用到Child類的父類中的FINAL_OBJECT變量,未使用到Child類中的變量,所以不會對Child類進行初始化,初始化的為其父類。

查看JVM加載情況

通過添加JVM參數-XX:+TraceClassLoading可以查看類加載情況

可見,會對Child,Parent類進行類加載操作,但是調用static方法,只有Parent類會調用static進行初始化操作。

總結

  1. 如果引用了常量池變量(String,以及基本類型相關變量),如果該變量為final static進行修飾的時候,則不會對類進行初始化操作
  2. 如果為非常量池變量,如果調用方存在父子類關係,則實際JVM會加載子類與父類,但是如果使用的為父類的final變量,並不會觸發類的初始化操作。
Exit mobile version