天天看點

使用javap深入了解Java整型常量和整型變量的差別

我下圖代碼第五行和第九行分别定義了一個整型變量和一個整型常量:

static final int number1 = 512;

static int number3 = 545;

Java程式員都知道兩者的差別。

下面我們就用javap将.class檔案反編譯出來然後深入研究Java裡整型變量和整型常量的差別。

使用指令行javap -c constant.ConstantFolding檢視.class檔案反編譯出來的位元組碼:

結果:

這些位元組碼指令的說明,在wikipedia裡有說明:

wiki: https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

咱們Java程式員不需要把它們都背下來,隻需要把這個網頁收藏起來,要用的時候當成字典來用就行:

sipush 545: 将整數545放置到棧上

putstatic #16:

将棧上的值545賦給目前類的靜态字段裡。

那麼putstatic #16裡的#16代表什麼含義?

我們再用javap -v 參數反編譯,就能看到這個類的常量池(Constant pool). 大家看下圖藍色高亮的一行:

constant/ConstantFolding.number3:I

說明#16代表類constant.ConstantFolding的成員number3,類型為I。

至此,這兩行位元組碼指令聯合起來,實際對應了我們寫的Java代碼:

我們繼續分析javap反編譯出來的位元組碼。

aload_0: 将序号為0的本地變量的引入加載到棧上

invokespecial: 調用對象執行個體上的成員方法,如果有傳回值,方法的傳回值存儲到棧上。具體調用的方法由#辨別,可在常量池中查詢到對應的方法名。

ldc: 将常量池上代号為#<數字>的常量的值從常量池加載到棧上。

我們從下圖的常量池清單能發現,序号為#29的常量318976正是整型常量number1(512)和整型常量(623)的積。由此可以看出, number1 * number2這個表達式,因為參與運算的兩個操作數通過STATIC和FINAL修飾成為了整型常量,是以其積在編譯期就能得到,是以編譯器在編譯時就計算出來,存儲在變量池裡,序号為#29。

那麼整型變量做乘法運算,對應的位元組碼又是什麼樣的呢?

從下圖序号為3的code開始:

getstatic #16: 将類的靜态成員#16加載到棧上。#16對應的成員為number3,值為545。

getstatic #18: 将類的靜态成員#18加載到棧上。#18對應的成員為number4,值為619。

imul: 執行棧上兩個整數的乘法運算。

istore_2: 将結果儲存到局部變量2裡。

此時,我們Java代碼裡的int product2 = number3 * number4就執行完了。

大家看到的剩下的藍色位元組碼,都對應了下面這行列印語句。

System.out.println("Value: " + product1 + " , " + product2);

從這些位元組碼也能看出,Java裡我們直接用加号進行字元串拼接操作,Java編譯器在編譯時,自動使用了StringBuilder進行優化。

既然整型變量的乘積需要列印出來,是以位元組碼的iload_2将之前用istore_2儲存在局部變量2中的計算結果又加載到棧上,這樣乘積結果最後就能輸出了。

希望通過這個簡單的例子,大家能學會用javap去深入了解一些Java和JVM的細節。

要擷取更多Jerry的原創技術文章,請關注公衆号"汪子熙"或者掃描下面二維碼: