jvm是如何執行i = i++ + ++i的,你知道嗎?
結果是多少?
public static void main(String[] args) { int i = 0; i = i++ + ++i; System.out.println(i); }//結果輸出 2
為什麼是2?
一個.java檔案首先要被編譯成.class檔案jvm才能夠執行,而jvm是根據java程式碼生成的位元組碼來確認他要如何執行程式的。說的再通俗一點就是,jvm看不懂java程式碼,他能看懂的是位元組碼,而編譯就是這麼一個翻譯的過程。
所以為了瞭解i = i++ + ++i
的執行原理,我們首先反彙編這段程式碼(請先編譯java檔案,Main.java是我的檔名):在命令列下輸入javap -c Main.class
可以看到位元組碼是:
0: iconst_0 1: istore_1 2: iload_1 3: iinc 1, 1 6: iinc 1, 1 9: iload_1 10: iadd 11: istore_1 12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 15: iload_1 16: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 19: return
不要怕,這其實很容易。為了不至於引入太多複雜概念,這裡只需要知道程式在執行時的兩個區域,一個叫做區域性變數表(Local Variable),一個叫做運算元棧(Operand Stack),前者的結構類似陣列,用來儲存區域性變數,後者的資料結構是棧,用來輔助執行指令。
我一條條解釋上述指令。先看下圖,因為這裡只用到了1個區域性變數表的位置,所以其他的就沒寫出來。
紅框的內容代表每條指令執行完,這兩個區域的值是多少。建議認真看下每一行的值是怎麼來的再往下看。實際上在執行到getstatic #2
這條命令的時候,我們的想知道的問題已經計算完了。主要關注這條命令以上的命令即可。並且我們的值最終也儲存在區域性變數表1號位置。
所以最終輸出2是理所當然的。
規律是什麼?
既然說class檔案是java檔案的“翻譯”過來的,那麼java程式碼和位元組碼總有對應關係吧?我們試著找一下。
我們說區域性變數表是用來存放區域性變數的,第二條指令又向區域性變數表中存入值了,根據指令的解釋很容易能夠猜到前兩條指令iconst_0 istore_1
對應java程式碼int i = 0;
。
我們透過最後的輸出知道了i的值是存在區域性變數表1中的,那麼istore_1
這個向區域性變數表1號位置賦值的語句一定就是將前面計算得到的i++ + ++i
的結果存進表的意思,也就是意味著在執行istore_1
語句時,運算元棧棧頂的元素就是我們計算的結果。
繼續往上推,iadd
指令執行的時候,棧頂的兩個元素一定一個就是i++ 另外一個就是++i的值。除去最開始的兩條指令,一共只剩下四條指令了分別是
iload_1 iinc 1, 1 iinc 1, 1 iload_1
猜也能猜出來前兩個對應一條指令,後兩個對應一條,畢竟這麼兩個相似的指令不可能翻譯出來位元組碼的命令數還不相等吧。問題是前兩個和後兩個誰對應i++誰對應++i。我們先回憶一下這兩條語句在java上有什麼不同,簡單說“i++是先用再加,++i是先加再用”。另外需要再講一個東西,我前面說運算元棧是用來輔助執行命令的,形象點理解就是運算元棧裡面的東西是馬上就要拿來用的,而區域性變數表是用來暫時先儲存下變數的。好了,回到我們剛才的問題,再想一下,你應該就能夠想到:前兩條指令對應i++而後兩條對應++i。前兩條位元組碼的含義是:我準備用1號變數,先放在棧裡(先用),好了,我已經放在棧裡了,你在區域性變數表裡可以加1了(再加)。後兩條位元組碼的含義是:你在區域性變數表裡先加1(先加),然後我要放在棧裡了(再用)。
這樣我們就把每條語句及其對應的位元組碼都找出來了,那麼規律到底是什麼?
我們現在用更加通俗的話來解釋i= i++ + ++i。首先這個語句等價於i = (i++) + (++i)。執行順序是:
計算i++
計算++i
將前兩個計算的結果加起來賦值給i
看起來好像在說廢話,那麼我們結合之前的位元組碼來分析。
步驟1還可以分成2步
將當前i的值,複製一份(假如複製出來的元素叫copy1 )。翻譯成程式碼:int copy1 = i; (最開始i為0)
將i的值加1。翻譯成程式碼:i++;(此時i為1)
步驟2同樣分成2步
將i的值加1。翻譯成程式碼:i++;(此時i為2)
將當前i的值,複製一份(假如複製出來的元素叫copy2 )。翻譯成程式碼:int copy2 = i;(此時i還是2)
將兩個計算結果加起來。i= copy1 + copy2 (也就是0+2)
總結起來:i= i++ + ++i真實執行過程的虛擬碼就是
int copy1 = i; i++ i++int copy2 = i i = copy1 + copy2;
那麼我們現在來試著算一下 i = ++i + i++ + ++i + ++i (i的初始值是0)
首先我們知道,i = ++i + i++ + ++i + ++i 等價於 i = (++i) + (i++) + (++i) + (++i)。
我們將四個括號裡的值分別起名為r1、r2、r3、r4。表示式從左向右計算。
首先計算r1:++i要先將i加1,然後賦值給r1,所以r1等於1。(執行完這條語句時i的值為1)
然後計算r2:i++要先將i的值賦值給r2,然後i字加1,所以r2等於1。(執行完這條語句時i的值為2)
然後計算r3:++i要先將i加1,然後賦值給r3,所以r3等於3。(執行完這條語句時i的值為3)
然後計算r4:++i要先將i加1,然後賦值給r4,所以r3等於4。(執行完這條語句時i的值為4)
然後計算r1+r2+r3+r4,等於1+1+3+4,結果為9
最後將9賦值給i。(其實此時i是有值的,就是之前的4,但是被剛賦值進來的9給覆蓋了,所以就沒能表現出來)
怎麼樣,你算出來了嗎?
這篇文章是最近學習jvm以來想通的一個以前一直不明白的問題,在此與大家分享下。如果有不懂的,可以下面留言。如有錯誤也請指出。
作者:Nancy945
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/756/viewspace-2809492/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 你真的懂 i++ 和 ++i 嗎?
- 你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?
- C++中的i++和++i你真的理解嗎?C++
- synchronized下的 i+=2 和 i++ i++執行結果居然不一樣synchronized
- 一道非常棘手的 常見Java 面試題:i++ 是執行緒安全的嗎?Java面試題執行緒
- 關於java中的i++和++iJava
- 你知道Spring中BeanFactoryPostProcessors是如何執行的嗎?SpringBean
- 位元組碼指令分析 ++i 和 i++
- C++複習整理---i++和++iC++
- i++引發的慘案
- 美團一面問我i++跟++i的區別是什麼
- 關於 i=i++ 問題、入棧順序
- Byteman 讓 i++ 百分百執行緒不安全執行緒
- Java中 i=i++ 問題底層原理解析Java
- Spring Boot 到底是怎麼執行的,你知道嗎?Spring Boot
- Spring Boot到底是怎麼執行的,你知道嗎?Spring Boot
- Java變數自增表示式 i = i++ 的底層邏輯(簡述)Java變數
- Mybatis如何執行Select語句,你真的知道嗎?MyBatis
- 你知道SSL是如何工作的嗎?
- 一個關於 i++ 和 ++i 的面試題打趴了所有人面試題
- 好程式設計師web前端教程分享初學者搞懂i++和++i程式設計師Web前端
- 【嗅探底層】你知道Synchronized作用是同步加鎖,可你知道它在JVM中是如何實現的嗎?synchronizedJVM
- ScheduledThreadPoolExecutor原始碼分析-你知道定時執行緒池是如何實現延遲執行和週期執行的嗎?thread原始碼執行緒
- 華為nova8和榮耀50最大的區別是什麼知道嗎?你i哪個
- C#自增運算子詳解:++i與i++的區別及應用場景C#
- 你知道前端是如何實現水印的嗎前端
- 你知道 koa 中介軟體執行原理嗎?
- 想知道你的Mac可以執行哪些版本的macOS嗎?Mac
- 你能手寫一個Promise嗎?Yes I promise。Promise
- 你真的知道計算機是如何進行減法運算的嗎?計算機
- 1v1影片原始碼,你知道如何實現多執行緒的順序執行嗎?原始碼執行緒
- Redis篇:單執行緒I/O模型Redis執行緒模型
- 說了這麼多次 I/O,但你知道它的原理麼
- 你知道《怪物獵人世界》是如何立項的嗎?
- 面試官又整新活,居然問我for迴圈用i++和++i哪個效率高?面試
- i = ++i
- mac 執行sed -i指令時,總是出現extra characters at the end of commandMac
- i7-8086K開蓋揭秘 i7-8086K是釺焊散熱嗎?