在測試類的加載過程中寫了一個測試代碼驗證類的初始化情況,當調用類的靜态方法時,即當使用了位元組碼invokestatic指令。這個時候類會初始化調用<clinit>方法,此時類肯定會進行加載。
這時肯定理所當然認為,如果調用final static的靜态變量時,類也肯定會加載。
測試代碼如下:
package simple02;
public class Demo01 {
public final static int NUM = 10000 ;
public final static String ABC = "1234567" ;
static {
System.out.println("demo 加載初始化");
}
public static void out(){
System.out.println("demo out");
}
}
package simple03;
import simple02.Demo01;
public class LoadingTest {
public static void main(String[] args) {
System.out.println("已經進入main方法");
try {
Thread.sleep(2000);
// int i = Demo01.NUM;
System.out.println( Demo01.ABC);
Thread.sleep(2000);
Demo01.out();
} catch (Exception e) {
e.printStackTrace();
}
}
}
當我打開-XX:+TraceClassLoading去觀察,發現在sout前并沒有加載該類,在休眠2秒後,Demo01才加載。正常邏輯如果有調用肯定會加載,即使不初始化也需要加載相關類。
[Loaded sun.util.locale.provider.LocaleResources$ResourceReference from D:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar]
1234567
[Loaded simple02.Demo01 from file:/D:/Coding/Java/jvm/target/classes/]
demo 加載初始化
demo out
面對這個結果測試了兩種類型,String類型,int類型結果都一樣,并沒有加載對應都類。
打開位元組碼檔案,檢視對應的位元組碼發現是ldc #9 <1234567>,說明在位元組碼檔案中Demo01.ABC是以常量存在。
0 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
3 ldc #3 <已經進入main方法>
5 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>
8 ldc2_w #5 <2000>
11 invokestatic #7 <java/lang/Thread.sleep : (J)V>
14 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
17 ldc #9 <1234567>
19 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>
22 ldc2_w #5 <2000>
25 invokestatic #7 <java/lang/Thread.sleep : (J)V>
28 invokestatic #10 <simple02/Demo01.out : ()V>
31 goto 39 (+8)
34 astore_1
35 aload_1
36 invokevirtual #12 <java/lang/Exception.printStackTrace : ()V>
39 return
通過反編譯代碼發現 System.out.println( Demo01.ABC); ==> System.out.println("1234567");
這裡表明javac的時候編譯器已經做了一些優化,而且是真的static final定義的常量進行優化。
////
public static void main(String[] args) {
System.out.println("已經進入main方法");
try {
Thread.sleep(2000L);
System.out.println("1234567"); //自動變成了字面量值
Demo01.out();
} catch (Exception var2) {
var2.printStackTrace();
}
}
/////
查了很多資料,發現這其實是編譯的優化,稱為内聯優化,簡單通俗來講就是把需要調用外部邏輯的步驟,嵌入到自身的邏輯中去,變成自身的一部分,之後不再調用該方法,進而節省額外開支。
那就是,用final static 修飾的變量,在編譯的時候,編譯器會把值放到調用者的常量池中,調用時候直接拿常量池的值,而不是去調用定義常量類的常量。
JIT編譯器也有使用内聯優化,有興趣的請自行查閱。