我下圖代碼第五行和第九行分別定義了一個整型變量和一個整型常量:java
static final int number1 = 512;程序員
static int number3 = 545;優化
Java程序員都知道二者的區別。ui
下面咱們就用javap將.class文件反編譯出來而後深刻研究Java裏整型變量和整型常量的區別。命令行
使用命令行javap -c constant.ConstantFolding查看.class文件反編譯出來的字節碼:code
結果:對象
這些字節碼指令的說明,在wikipedia裏有說明:ip
wiki: https://en.wikipedia.org/wiki/Java_bytecode_instruction_listingsci
我們Java程序員不須要把它們都背下來,只須要把這個網頁收藏起來,要用的時候當成字典來用就行:字符串
sipush 545: 將整數545放置到棧上
putstatic #16:
將棧上的值545賦給當前類的靜態字段裏。
那麼putstatic #16裏的#16表明什麼含義?
咱們再用javap -v 參數反編譯,就能看到這個類的常量池(Constant pool). 你們看下圖藍色高亮的一行:
constant/ConstantFolding.number3:I
說明#16表明類constant.ConstantFolding的成員number3,類型爲I。
至此,這兩行字節碼指令聯合起來,實際對應了咱們寫的Java代碼:
static int number3 = 545;
咱們繼續分析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的原創技術文章,請關注公衆號"汪子熙"或者掃描下面二維碼: