天天看點

final static修飾的變量的小疑惑

作者:IT農民工1024

在測試類的加載過程中寫了一個測試代碼驗證類的初始化情況,當調用類的靜态方法時,即當使用了位元組碼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編譯器也有使用内聯優化,有興趣的請自行查閱。