參考內容:
- 深入理解Java虛擬機(JVM高級特性與最佳實踐) ——周志明老師
- 尚硅谷深入理解JVM教學視頻——宋紅康老師
在本文展開前,讀者需要了解一些位元組碼有關的知識,以及JVM虛擬機堆疊中堆疊幀的區域變數表和運算元堆疊等知識,筆者在這里只給出一些大概的簡述,
位元組碼
-
Java位元組碼對于虛擬機,就好像匯編語言對于計算機,屬于基本執行指令,
-
虛擬機的指令由一個位元組長度的、代表著某種特定操作含義的數字(稱為操作碼,Opcode)以及跟隨其后的零至多個代表此操作所需引數(稱為運算元,Operands)而構成,由于Java虛擬機采用面向運算元堆疊而不是暫存器的結構,所以大多數的指令都不包含運算元,只有一個操作碼,
區域變數表
區域變數表:Local Variables,被稱之為區域變數陣列或本地變數表
定義為一個數字陣列,主要用于存盤方法引數和定義在方法體內的區域變數,這些資料型別包括各類基本資料型別、物件參考(reference),以及returnAddress型別,
由于區域變數表是建立在執行緒的堆疊上,是執行緒的私有資料,因此不存在資料安全問題,
區域變數表所需的容量大小是在編譯期確定下來的,并保存在方法的Code屬性的maximum local variables資料項中,在方法運行期間是不會改變區域變數表的大小的,
運算元堆疊
運算元堆疊:Operand Stack ,使用陣列實作的,
每一個獨立的堆疊幀除了包含區域變數表以外,還包含一個后進先出(Last - In - First -Out)的 運算元堆疊,也可以稱之為 運算式堆疊(Expression Stack)
運算元堆疊,在方法執行程序中,根據位元組碼指令,往堆疊中寫入資料或提取資料,即入堆疊(push)和 出堆疊(pop)
- 某些位元組碼指令將值壓入運算元堆疊,其余的位元組碼指令將運算元取出堆疊,使用它們后再把結果壓入堆疊
- 比如:執行復制、交換、求和等操作
運算元堆疊,主要用于保存計算程序的中間結果,同時作為計算程序中變數臨時的存盤空間,
接下來就是本文的正式內容,首先,我們先給出兩個結論:
- i++與++i在不同情況下可能會有不同的結論;
- 實體變數/類變數的i++并不是一個原子性的操作,
首先我們看一下i++與++i的決議:
當i++或者++i沒有涉及到其他操作時,兩者是沒有區別的,
// i++
public void method1(){
int i = 10;
i++;
}
// ++i
public void method2(){
int i = 10;
++i;
}
對應的位元組碼指令操作為:
// method1
0 bipush 10 // 將10這個整數壓入運算元堆疊
2 istore_1 // 將運算元堆疊堆疊頂元素保存到區域變數表中索引為1處
3 iinc 1 by 1 // 區域變數表中索引為1處的元素,也就是i進行自增(這一步是在區域變數表上直接進行的,與運算元堆疊無關)
6 return // 方法回傳
// method2
0 bipush 10
2 istore_1
3 iinc 1 by 1 // ++i
6 return
其中關于給出的具體位元組碼細節以及堆疊幀中運算元堆疊、區域變數表在本文開頭給出了一些簡介,具體內容不展開描述,讀者可翻閱與之有關的資料,
通過反編譯可以看出,i++與++i的位元組碼在沒有和其他操作組合時,位元組碼是完全相同的,
當i++或者++i涉及到其他操作時,兩者的位元組碼會有一些改變,
public void method7(){
int i = 10;
int a = i++;
int j = 20;
int b = ++j;
}
對應的的位元組碼指令:
0 bipush 10
2 istore_1
3 iload_1 // i++先從區域變數表中讀取i到運算元堆疊
4 iinc 1 by 1 // i直接在區域變數表上進行自增
7 istore_2 // 將運算元堆疊上讀取到的i的值賦值給a,也就是10
8 bipush 20
10 istore_3
11 iinc 3 by 1 // ++j則先在區域變數表上進行自增
14 iload_3 // 再從區域變數表中讀取j到運算元堆疊
15 istore 4 // 將運算元堆疊上讀取到的j的值賦值給b,也就是21
17 returns
通過反編譯可以看出,i++與++i的位元組碼在沒有和其他操作組合時,i++是先取值再自增,而++i是先自增再取值,
還有一個關于i=i++的決議:
public void method8(){
int i = 10;
i = i++;
System.out.println(i);//10
}
對應的位元組碼指令:
0 bipush 10
2 istore_1
3 iload_1 // 從區域變數表中讀取i到運算元堆疊
4 iinc 1 by 1 // i直接在區域變數表上進行自增,此時i = 11
7 istore_1 // 將之前運算元堆疊上讀取到的i的值賦值給i,之前自增的值被覆寫了,i = 10
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #5 <java/io/PrintStream.println>
15 return
然后看一下實體變數i++這行代碼的對應的位元組碼
首先我們定義一個類
/**
* @author XiaoLe
* @create 2020-12-04 21:15
* @description
*/
public class Test {
private int i = 0;
public void test(){
i++;
}
}
通過反編譯查看test方法中的位元組碼:
0 aload_0
1 dup
2 getfield #2 <day001/Test.i> // 獲取到Test的i變數的值
5 iconst_1 // 將int型別常量1壓入堆疊
6 iadd // 堆疊頂兩個元素相加后回傳值入堆疊
7 putfield #2 <day001/Test.i>
10 return
可以看到,i++這行代碼被拆分為三個位元組碼,所以在一些并發情況下,i++如果不做同步處理,就可能會出現資料非一致性,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/230281.html
標籤:java
