這是由一個“無聊”的問題引發(fā)的故事:方法ipp和ppi分別會打印什么結(jié)果?

public class Opcode { public static void main(String[] args) {
System.out.println("hello wang ni ma");
} public void ipp(){ int i = 0;
i = i++;
System.out.println(i);
} public void ppi(){ int i = 0;
i = ++i;
System.out.println(i);
}
}當(dāng)然了,把兩個方法放在一起,憑借些許的邏輯思維分析,可以很快給出答案: 0 1
那JVM為什么會執(zhí)行出這樣的結(jié)果呢,本文將結(jié)合 字節(jié)碼 和 虛擬機棧 做出解釋。
javap是JDK自帶的反匯編器,可以查看java編譯器為我們生成的字節(jié)碼。通過它,我們可以對照源代碼和字節(jié)碼,從而了解很多編譯器內(nèi)部的工作。
Java 程序編譯之后就變成了一條條字節(jié)碼指令,其形式類似匯編,但和匯編有不同之處:
匯編指令的操作數(shù)存放在數(shù)據(jù)段和寄存器中,可通過存儲器或寄存器尋址找到需要的操作數(shù);
Java 字節(jié)碼指令的操作數(shù)存放在操作數(shù)棧中(可以理解為JVM內(nèi)部虛擬寄存器),當(dāng)執(zhí)行某條帶 n 個操作數(shù)的指令時,就從棧頂取 n 個操作數(shù),然后把指令的計算結(jié)果(如果有的話)入棧。
由于操作數(shù)棧是內(nèi)存空間,所以字節(jié)碼指令不必擔(dān)心不同機器上寄存器以及機器指令的差別,從而做到了平臺無關(guān)。
Java虛擬機的指令由一個字節(jié)長度的、代表著某種特定操作含義的操作碼(Opcode)以及跟隨其后的零至多個代表此操作所需參數(shù)的操作數(shù)(Operands)所構(gòu)成。
列舉本文用到的基本指令:
加載和存儲指令
將一個局部變量加載到操作棧:iload_<n>
將一個數(shù)值從操作數(shù)棧存儲到局部變量表:istore_<n>
將一個常量加載到操作數(shù)棧:iconst_<i>
運算指令 (運算之后的結(jié)果會自動入棧)
局部變量自增指令:iinc
對于大部分與數(shù)據(jù)類型相關(guān)的字節(jié)碼指令,他們的操作碼助記符中都有特殊的字符來表明專門為哪種數(shù)據(jù)類型服務(wù):
i -- int
l -- long
s -- short
b -- byte
c -- char
f -- float
d -- double
a -- reference
我們用javac編譯上面的Opcode.java,然后“javap -c”查看字節(jié)碼:

javap命令加入“-v”可以看到更詳細的信息(常量池) :

在看圖之前我們先了解幾個概念:
Java虛擬機(JVM)是基于棧結(jié)構(gòu)的,其中的“棧”指的就是操作數(shù)棧。
在代碼的實際運行中,每個線程都會創(chuàng)建一個JVM棧存儲棧幀(frame)。每當(dāng)有方法調(diào)用時,frame就會被創(chuàng)建;當(dāng)這個方法返回時,frame出棧。
一個frame由三部分組成:操作數(shù)棧(Oprand Stack)、局部變量表(Local Variable Table)、當(dāng)前方法所在類的運行常量池的引用(The Reference of Constant Pool)。
局部變量表,存儲的是方法的參數(shù)和局部變量的值。存儲參數(shù)的索引從0開始,如果是構(gòu)造方法或者實例化方法的frame,那么局部變量數(shù)組的“0”處存儲的是“this”引用,然后再從“1”開始存儲形參和局部變量;如果是靜態(tài)方法的frame,局部變量表不會存儲“this”引用,而是從“0”開始存儲形參和局部變量。
操作數(shù)棧,臨時存儲參與運算的數(shù)值,然后進行相關(guān)操作。和局部變量表一樣,操作數(shù)棧也是一個以字長為單位的數(shù)組。但是和前者不同的是,它不是通過索引來訪問,而是通過標(biāo)準(zhǔn)的棧操作壓棧/出棧來訪問的。
常量池,存儲在JVM內(nèi)存線程共享區(qū)的“方法區(qū)”,在類初始化的時候,會為給出的常量分配一個常量池,并且為每一個常量給出引用。
ipp()
把常量“0”加載到操作數(shù)棧,指令“iconst_0”中的“0”代表int常量“0”
操作數(shù)棧:[0]
局部變量表:[this]

“i = i++;”進行了四次操作:
1 “istore_1”將操作數(shù)棧中棧頂?shù)膇nt壓入局部變量表“1”的位置
操作數(shù)棧:[]
局部變量表:[this, 0]
2 “iload_1”將局部變量表“1”處的int加載到操作數(shù)棧
操作數(shù)棧:[0]
局部變量表:[this, 0]
3 “iinc 1, 1”將局部變量表“1”處的int做自增運算,結(jié)果自動入棧
操作數(shù)棧:[0]
局部變量表:[this, 1]
4 “istore_1”將操作數(shù)棧“1”處的int壓入局部變量表
操作數(shù)棧:[]
局部變量表:[this, 0]

“System.out.println(i);”進行了三次操作:
1 “getstatic #2”指向常量池中的第2個位置,載入“System.out”域
2 “iload_1”將局部變量表“1”處的int加載到操作數(shù)棧
操作數(shù)棧:[0]
局部變量表:[this, 0]
3 “invokevirtul #5”指向常量池中的第5個位置,”調(diào)用實例方法“println”打印操作數(shù)棧的數(shù)值“0”

至此,ipp()方法的分析完成了,理解之后,反觀ppi()方法的字節(jié)碼信息,有一處不同:

相當(dāng)于“i = i++”操作是先加載了局部變量表中的“0”到操作數(shù)棧,然后在局部變量表中做自增運算;而“i = ++i”是先在局部變量表中做自增運算,此時的值已經(jīng)變成“1”,然后再把局部變量表中的“1”加載到操作數(shù)棧。這也就印證了坊間流傳的“i++是先賦值后運算,++i是先運算后賦值”這一說法。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
分享題目:JVM虛擬機棧——JAVA方法的消亡史-創(chuàng)新互聯(lián)
網(wǎng)站鏈接:http://chinadenli.net/article48/didoep.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、App設(shè)計、ChatGPT、商城網(wǎng)站、靜態(tài)網(wǎng)站、自適應(yīng)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容